Skip to content

Commit

Permalink
feat: 1.vue/5.components
Browse files Browse the repository at this point in the history
  • Loading branch information
ubugeeei committed Sep 6, 2024
1 parent c0cca38 commit e1f4efb
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 36 deletions.
6 changes: 3 additions & 3 deletions content/1.vue/5.components/.template/files/app.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'
import Child from './Child.vue'
const name = ref('John Doe')
Expand All @@ -12,9 +12,9 @@ function updateName(value: string) {
<div>
<h1>Parent Component</h1>
<p>Hi, {{ name }} 👋</p>
<ChildComponent
<Child
message="Hello from Parent!"
:name="name"
:name
@update:name="updateName"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
<script setup lang="ts">
defineProps<{
message: string
name: string
}>()
const name = defineModel<string>()
const emit = defineEmits<{
'update:name': [name: string]
}>()
defineSlots<{
paragraph: () => any
}>()
</script>

<template>
<div class="child-component">
<h2>Child Component</h2>
<p>{{ message }}</p>
<p><slot name="paragraph" /></p>
<input
v-model="name"
type="text"
:value="name"
@input="emit('update:name', $event.target.value)"
>
</div>
</template>
Expand Down
24 changes: 19 additions & 5 deletions content/1.vue/5.components/.template/solutions/app.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'
import Child from './Child.vue'
const name = ref('John Doe')
function updateName(value: string) {
name.value = value
}
</script>

<template>
<div>
<h1>Parent Component</h1>
<p>Hi, {{ name }} 👋</p>
<ChildComponent
v-model="name"
message="Hello from Parent!"
/>
<Child
:name
@update:name="updateName"
>
<template #paragraph>
Hello from <span class="red--text">Parent!</span>
</template>
</Child>
</div>
</template>

<style scoped>
.red--text {
color: red;
}
</style>
128 changes: 103 additions & 25 deletions content/1.vue/5.components/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Vue.js のコンポーネントは、UI を小さな再利用可能な部分に

## 基本的な SFC の構造

SFC は基本的に以下のような `<script>`, `<template>`, `<style>` の 3 つのセクションで構成されます。
SFC は基本的に以下のような `<script setup>`, `<template>`, `<style>` の 3 つのセクションで構成されます。

```vue
<script setup lang="ts">
Expand All @@ -36,7 +36,7 @@ p {
</style>
```

この例では、`<script>`, `<template>`, `<style>` の 3 つのセクションが使われています。
この例では、`<script setup>`, `<template>`, `<style>` の 3 つのセクションが使われています。

- `<script setup>`: コンポーネントのロジック部分を定義します。`<script setup>` を使用することで、Composition API を簡潔に書くことができます。
- `<template>`: コンポーネントのビュー部分を定義します。
Expand All @@ -48,70 +48,148 @@ p {

```vue
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'
import Child from './Child.vue'
</script>
<template>
<ChildComponent />
<Child />
</template>
```

## コンポーネント間のデータの受け渡し

Vue コンポーネント間でデータをやり取りする基本的な方法として、`props``emit` を使用します。

- `props`: 親コンポーネントから子コンポーネントにデータを渡すための方法です。
- `emit`: 子コンポーネントから親コンポーネントにイベントを発火するための方法です。
### Props

それぞれ `defineProps`, `defineEmits` で登録します。\
使い方は右側のプレイグラウンド、または [API ドキュメント](https://ja.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)から確認できます。
親コンポーネントから子コンポーネントにデータを渡すための方法です。

## 双方向バインディング

コンポーネント上で `v-model` を使うことで双方向バインディングを実装できます。\
以下の例では、コンポーネント中で宣言された `value``<input>` の値とバインドされ、`<input>` の入力値が `value` に反映されます。
まずは子コンポーネント側で `defineProps` マクロを使用し、受け取りたいデータを定義します。

```vue
<!-- Child.vue -->
<script setup lang="ts">
const value = ref('')
defineProps<{ message: string }>()
</script>
```

次に親コンポーネント側で、子コンポーネントにデータを渡すために `v-bind` ディレクティブを使用します。\
`:props名="データ"` という形式で、子コンポーネントにデータを渡すことができます。

```vue
<!-- Parent.vue -->
<template>
<input v-model="value" type="text">
<Child :message="message" />
</template>
```

また、SFC で `defineModel` を使うことで、親コンポーネントから `v-model` 経由で使用できる双方向バインディングの `props` を宣言できます。
また、props 名とデータの変数名が同名の場合は省略記法を使うことができます。

```vue
<!-- Parent.vue -->
<template>
<Child :message />
</template>
```

### Emit

子コンポーネントから親コンポーネントにイベントを発火するための方法です。

まずは子コンポーネント側で `defineEmits` マクロを使用し、発火したいイベントを定義します。
emit 関数を用いて、イベントを発火することができます。

```vue
<!-- Child.vue -->
<script setup lang="ts">
const localValue = defineModel<string>()
const emit = defineEmits<{ sendMessage: [] }>()
</script>
<template>
<input v-model="localValue" type="text">
<button type="button" @click="emit('sendMessage')">
Click me
</button>
</template>
```

発火されたイベントは親コンポーネント側で `v-on` ディレクティブを使用して受け取ることができます。

```vue
<!-- Parent.vue -->
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'
function handleSendMessage() {
console.log('Message sent!')
}
</script>
const parentValue = ref('Initial Value')
<template>
<Child @send-message="handleSendMessage" />
</template>
```

以下のように、イベント発火時に子コンポーネントからデータを受け渡すこともできます。

```vue
<!-- Child.vue -->
<script setup lang="ts">
const emit = defineEmits<{ sendMessage: [string] }>()
</script>
<template>
<ChildComponent v-model="parentValue" />
<button type="button" @click="emit('sendMessage', 'Hello, Vue!')">
Click me
</button>
</template>
```

## チャレンジ
```vue
<!-- Parent.vue -->
<script setup lang="ts">
function handleSendMessage(message: string) {
console.log(message)
}
</script>
プレイグラウンドの `ChildComponent.vue``props``emit` を使って双方向バインディングを実現しています。\
これを `defineModel` を使って簡潔に書き直してみましょう。
<template>
<Child @send-message="handleSendMessage" />
</template>
```

もし手詰まりになったら、解決策を確認するためのボタンをクリックして、ヒントを得ることができます。
それぞれの詳しい [API ドキュメント](https://ja.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)から確認することができます。

## チャレンジ

:ButtonShowSolution{.bg-faded.px4.py2.rounded.border.border-base.hover:bg-active.hover:text-primary.hover:border-primary:50}
右のプレイグラウンドでは、props と emit を使ってコンポーネント間のデータの受け渡しを行っています。\
Vue.js では [スロット](https://ja.vuejs.org/guide/components/slots.html) という機能を利用することで、親コンポーネントからコンポーネントにテンプレートを挿入することができます。\
右のプレイグラウンドを編集して、スロットを使ったテンプレートの挿入を行ってみましょう。

1. 子コンポーネント (`Child.vue`) でスロットの定義を行う\
[defineSlot マクロ](https://ja.vuejs.org/api/sfc-script-setup.html#defineslots) を使うことにより、型安全なスロットを定義することができます。\
定義ができたら、template 内で `slot` タグを配置することで渡されたテンプレートの挿入を行うことができます。

```vue
<script setup lang="ts">
defineSlots<{ paragraph: () => any }>()
</script>
<template>
<h2>Child Component</h2>
<p><slot name="paragraph" /></p>
</template>
```

2. 親コンポーネント (`app.vue`) で slot にテンプレートを挿入する\
親コンポーネント側で、子コンポーネントにテンプレートを挿入するために `v-slot` ディレクティブを使用します。\
(ここでは `v-slot` の省略記法である `#` を使用しています)

```vue
<template>
<Child>
<template #paragraph>
Hello from <span class="red--text">Parent!</span>
</template>
</Child>
</template>
```

:ButtonShowSolution{.bg-faded.px4.py2.rounded.border.border-base.hover:bg-active.hover:text-primary.hover:border-primary:50}

0 comments on commit e1f4efb

Please sign in to comment.