Skip to content

Commit 8c957b8

Browse files
committed
feat: 1.vue/5.components
1 parent c0cca38 commit 8c957b8

File tree

6 files changed

+138
-38
lines changed

6 files changed

+138
-38
lines changed

Diff for: content/1.vue/4.composition-api/index.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
- **関数として定義**: コンポーザブルは通常、関数として定義され、必要な状態やメソッドを返します。この関数は Vue の Composition API を使用して内部で状態管理や副作用を処理します。
99
- **明示的な依存関係**: コンポーザブルを使うことで、依存関係が明示的になり、どのロジックや状態がどのコンポーネントで使用されているかが明確になります。
1010

11-
Nuxt では、`composables/` ディレクトリに コンポーザブルなロジックを格納することが多く[自動インポート](https://nuxt.com/docs/examples/features/auto-imports) の対象になります。
11+
Nuxt では、`composables/` ディレクトリにコンポーザブルなロジックを格納することが多く[自動インポート](https://nuxt.com/docs/examples/features/auto-imports) の対象になります。
1212

1313
## チャレンジ
1414

15-
それでは、これらの特徴を踏まえて以下のステップでロジックを コンポーザブルとして切り出し、再利用してみましょう。
15+
それでは、これらの特徴を踏まえて以下のステップでロジックをコンポーザブルとして切り出し、再利用してみましょう。
1616

1717
1. 既存の vue ファイル(`app.vue`)の確認してください。
1818
2. カウンターロジックを `composables/useCounter.ts` に切り出してください。

Diff for: content/1.vue/5.components/.template/files/app.vue

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import ChildComponent from './ChildComponent.vue'
2+
import Child from './Child.vue'
33
44
const name = ref('John Doe')
55
@@ -12,9 +12,9 @@ function updateName(value: string) {
1212
<div>
1313
<h1>Parent Component</h1>
1414
<p>Hi, {{ name }} 👋</p>
15-
<ChildComponent
15+
<Child
1616
message="Hello from Parent!"
17-
:name="name"
17+
:name
1818
@update:name="updateName"
1919
/>
2020
</div>

Diff for: content/1.vue/5.components/.template/solutions/ChildComponent.vue renamed to content/1.vue/5.components/.template/solutions/Child.vue

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
<script setup lang="ts">
22
defineProps<{
33
message: string
4+
name: string
45
}>()
56
6-
const name = defineModel<string>()
7+
const emit = defineEmits<{
8+
'update:name': [name: string]
9+
}>()
10+
11+
defineSlots<{
12+
paragraph: () => any
13+
}>()
714
</script>
815

916
<template>
1017
<div class="child-component">
1118
<h2>Child Component</h2>
12-
<p>{{ message }}</p>
19+
<p><slot name="paragraph" /></p>
1320
<input
14-
v-model="name"
1521
type="text"
22+
:value="name"
23+
@input="emit('update:name', $event.target.value)"
1624
>
1725
</div>
1826
</template>
+19-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
11
<script setup lang="ts">
2-
import ChildComponent from './ChildComponent.vue'
2+
import Child from './Child.vue'
33
44
const name = ref('John Doe')
5+
6+
function updateName(value: string) {
7+
name.value = value
8+
}
59
</script>
610

711
<template>
812
<div>
913
<h1>Parent Component</h1>
1014
<p>Hi, {{ name }} 👋</p>
11-
<ChildComponent
12-
v-model="name"
13-
message="Hello from Parent!"
14-
/>
15+
<Child
16+
:name
17+
@update:name="updateName"
18+
>
19+
<template #paragraph>
20+
Hello from <span class="red--text">Parent!</span>
21+
</template>
22+
</Child>
1523
</div>
1624
</template>
25+
26+
<style scoped>
27+
.red--text {
28+
color: red;
29+
}
30+
</style>

Diff for: content/1.vue/5.components/index.md

+103-25
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Vue.js のコンポーネントは、UI を小さな再利用可能な部分に
99

1010
## 基本的な SFC の構造
1111

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

1414
```vue
1515
<script setup lang="ts">
@@ -36,7 +36,7 @@ p {
3636
</style>
3737
```
3838

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

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

4949
```vue
5050
<script setup lang="ts">
51-
import ChildComponent from './ChildComponent.vue'
51+
import Child from './Child.vue'
5252
</script>
5353
5454
<template>
55-
<ChildComponent />
55+
<Child />
5656
</template>
5757
```
5858

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

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

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

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

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

7469
```vue
70+
<!-- Child.vue -->
7571
<script setup lang="ts">
76-
const value = ref('')
72+
defineProps<{ message: string }>()
7773
</script>
74+
```
7875

76+
次に親コンポーネント側で、子コンポーネントにデータを渡すために `v-bind` ディレクティブを使用します。\
77+
`:props名="データ"` という形式で、子コンポーネントにデータを渡すことができます。
78+
79+
```vue
80+
<!-- Parent.vue -->
7981
<template>
80-
<input v-model="value" type="text">
82+
<Child :message="message" />
8183
</template>
8284
```
8385

84-
また、SFC で `defineModel` を使うことで、親コンポーネントから `v-model` 経由で使用できる双方向バインディングの `props` を宣言できます。
86+
また、props 名とデータの変数名が同名の場合は省略記法を使うことができます。
87+
88+
```vue
89+
<!-- Parent.vue -->
90+
<template>
91+
<Child :message />
92+
</template>
93+
```
94+
95+
### Emit
96+
97+
子コンポーネントから親コンポーネントにイベントを発火するための方法です。
98+
99+
まずは子コンポーネント側で `defineEmits` マクロを使用し、発火したいイベントを定義します。
100+
emit 関数を用いて、イベントを発火することができます。
85101

86102
```vue
87103
<!-- Child.vue -->
88104
<script setup lang="ts">
89-
const localValue = defineModel<string>()
105+
const emit = defineEmits<{ sendMessage: [] }>()
90106
</script>
91107
92108
<template>
93-
<input v-model="localValue" type="text">
109+
<button type="button" @click="emit('sendMessage')">
110+
Click me
111+
</button>
94112
</template>
95113
```
96114

115+
発火されたイベントは親コンポーネント側で `v-on` ディレクティブを使用して受け取ることができます。
116+
97117
```vue
98118
<!-- Parent.vue -->
99119
<script setup lang="ts">
100-
import ChildComponent from './ChildComponent.vue'
120+
function handleSendMessage() {
121+
console.log('Message sent!')
122+
}
123+
</script>
101124
102-
const parentValue = ref('Initial Value')
125+
<template>
126+
<Child @send-message="handleSendMessage" />
127+
</template>
128+
```
129+
130+
以下のように、イベント発火時に子コンポーネントからデータを受け渡すこともできます。
131+
132+
```vue
133+
<!-- Child.vue -->
134+
<script setup lang="ts">
135+
const emit = defineEmits<{ sendMessage: [string] }>()
103136
</script>
104137
105138
<template>
106-
<ChildComponent v-model="parentValue" />
139+
<button type="button" @click="emit('sendMessage', 'Hello, Vue!')">
140+
Click me
141+
</button>
107142
</template>
108143
```
109144

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

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

117-
:ButtonShowSolution{.bg-faded.px4.py2.rounded.border.border-base.hover:bg-active.hover:text-primary.hover:border-primary:50}
162+
右のプレイグラウンドでは、props と emit を使ってコンポーネント間のデータの受け渡しを行っています。\
163+
Vue.js では [スロット](https://ja.vuejs.org/guide/components/slots.html) という機能を利用することで、親コンポーネントからコンポーネントにテンプレートを挿入することができます。\
164+
右のプレイグラウンドを編集して、スロットを使ったテンプレートの挿入を行ってみましょう。
165+
166+
1. 子コンポーネント (`Child.vue`) でスロットの定義を行う\
167+
[defineSlot マクロ](https://ja.vuejs.org/api/sfc-script-setup.html#defineslots) を使うことにより、型安全なスロットを定義することができます。\
168+
定義ができたら、template 内で `slot` タグを配置することで渡されたテンプレートの挿入を行うことができます。
169+
170+
```vue
171+
<script setup lang="ts">
172+
defineSlots<{ paragraph: () => any }>()
173+
</script>
174+
175+
<template>
176+
<h2>Child Component</h2>
177+
<p><slot name="paragraph" /></p>
178+
</template>
179+
```
180+
181+
2. 親コンポーネント (`app.vue`) で slot にテンプレートを挿入する\
182+
親コンポーネント側で、子コンポーネントにテンプレートを挿入するために `v-slot` ディレクティブを使用します。\
183+
(ここでは `v-slot` の省略記法の `#` を使用しています)
184+
185+
```vue
186+
<template>
187+
<Child>
188+
<template #paragraph>
189+
Hello from <span class="red--text">Parent!</span>
190+
</template>
191+
</Child>
192+
</template>
193+
```
194+
195+
:ButtonShowSolution{.bg-faded.px4.py2.rounded.border.border-base.hover:bg-active.hover:text-primary.hover:border-primary:50}

0 commit comments

Comments
 (0)