Skip to content

Commit 4c2b5a0

Browse files
Feature/split component chapter (#254)
* split component chapter * rename 8.componentization-2 -> 9.componentization-2 * fix index * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent f1c957f commit 4c2b5a0

File tree

19 files changed

+649
-194
lines changed

19 files changed

+649
-194
lines changed

content/ja/9.workspace/1.todo-list/1.index.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ unlisted: true
1515
本チュートリアルは、アプリを完成させるステップごとに章が分かれています。
1616
各章では、Vue.js の主要な機能や概念をひとつずつ学びながら進めます。
1717

18-
| 項目 | 学べること |
19-
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------ |
20-
| [1. リアクティビティ (1)](/workspace/todo-list/reactivity-1/) | `ref` による状態管理と、データ変更時にビューが自動更新される仕組み |
21-
| [2. リストレンダリング](/workspace/todo-list/list-rendering/) | `v-for` を使った配列やリストの繰り返し表示 |
22-
| [3. 条件付きレンダリング](/workspace/todo-list/conditional/) | `v-if` / `v-else` による表示・非表示の切り替え |
23-
| [4. コンポーネント化](/workspace/todo-list/componentization-1/) | `props``emit``v-on` を使ったコンポーネント分割とイベント伝達 |
24-
| [5. 双方向データバインディング](/workspace/todo-list/v-model/) | `v-model``defineModel` によるフォーム入力とのデータ連動 |
25-
| [6. リアクティビティ (2)](/workspace/todo-list/reactivity-2/) | `computed` によるリアクティブな算出プロパティの利用 |
26-
| [7. 独自コンポーネントでの v-model](/workspace/todo-list/componentization-2/) | 独自コンポーネントで双方向バインディングを実装する方法 |
27-
| [8. スロットコンテンツ・スロットアウトレット](/workspace/todo-list/v-slot/) | コンポーネントにスロット機能をつける |
18+
| 項目 | 学べること |
19+
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
20+
| [1. リアクティビティ (1)](/workspace/todo-list/reactivity-1/) | `ref` による状態管理と、データ変更時にビューが自動更新される仕組み |
21+
| [2. リストレンダリング](/workspace/todo-list/list-rendering/) | `v-for` を使った配列やリストの繰り返し表示 |
22+
| [3. 条件付きレンダリング](/workspace/todo-list/conditional/) | `v-if` / `v-else` による表示・非表示の切り替え |
23+
| [4. コンポーネント化](/workspace/todo-list/componentization-1/) | コンポーネント分割 |
24+
| [5. コンポーネント間でのデータ受け渡し](/workspace/todo-list/componentization-2/) | `props``emit``v-on` をコンポーネント間でのデータ受け渡し |
25+
| [6. 双方向データバインディング](/workspace/todo-list/v-model/) | `v-model``defineModel` によるフォーム入力とのデータ連動 |
26+
| [7. リアクティビティ (2)](/workspace/todo-list/reactivity-2/) | `computed` によるリアクティブな算出プロパティの利用 |
27+
| [8. 独自コンポーネントでの v-model](/workspace/todo-list/componentization-2/) | 独自コンポーネントで双方向バインディングを実装する方法 |
28+
| [9. スロットコンテンツ・スロットアウトレット](/workspace/todo-list/v-slot/) | コンポーネントにスロット機能をつける |
2829

2930
## 使用する技術
3031

content/ja/9.workspace/1.todo-list/5.componentization-1/.template/files/components/TodoList.vue

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
11
<script setup lang="ts">
2-
/**
3-
* Props
4-
*/
5-
// TODO: definePropsを使用してtodosを親から受け取る
6-
7-
/**
8-
* Emits
9-
*/
10-
// TODO: defineEmitsを使用してクリックイベントと必要なデータを親に渡す
11-
122
/**
133
* Types
144
*/

content/ja/9.workspace/1.todo-list/5.componentization-1/.template/solutions/app.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ interface Todo {
5858
</header>
5959

6060
<main>
61-
<TodoList :todos="todos" @update-done="updateDone" />
61+
<TodoList />
6262
</main>
6363

6464
<footer class="footer">

content/ja/9.workspace/1.todo-list/5.componentization-1/.template/solutions/components/TodoList.vue

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,4 @@
11
<script setup lang="ts">
2-
/**
3-
* Props
4-
*/
5-
defineProps<{
6-
todos: Todo[]
7-
}>()
8-
9-
/**
10-
* Emit
11-
*/
12-
const emit = defineEmits<{
13-
updateDone: [number, boolean]
14-
}>()
15-
162
/**
173
* Types
184
*/
@@ -38,19 +24,10 @@ interface Todo {
3824
<tbody>
3925
<tr v-for="todo in todos" :key="todo.id">
4026
<td class="text-center">
41-
<button
42-
v-if="todo.done" type="button"
43-
class="button-icon"
44-
@click="emit('updateDone', todo.id, false)"
45-
>
27+
<button v-if="todo.done" type="button" class="button-icon" @click="updateDone(todo.id, false)">
4628
4729
</button>
48-
<button
49-
v-else
50-
type="button"
51-
class="button-icon"
52-
@click="emit('updateDone', todo.id, true)"
53-
>
30+
<button v-else type="button" class="button-icon" @click="updateDone(todo.id, true)">
5431
5532
</button>
5633
</td>

content/ja/9.workspace/1.todo-list/5.componentization-1/index.md

Lines changed: 5 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -40,156 +40,13 @@ import Child from './Child.vue'
4040

4141
1. `app.vue``<table></table>``TodoList.vue``<template>`内に移動しましょう
4242
2. `app.vue``/* --- table start --- */`から`/* --- table last --- */`までを`TodoList.vue``<style scoped>`内に移動しましょう
43-
44-
## コンポーネント間のデータの受け渡し
45-
46-
app.vueがすっきりとしました。
47-
48-
ただこのままでは、TodoList.vue側でapp.vueに定義された値にアクセスすることができません。
49-
50-
Vue コンポーネント間でデータをやり取りする基本的な方法として、`props``emit` を使用します。
51-
52-
### Props
53-
54-
親コンポーネントから子コンポーネントにデータを渡すための方法です。
55-
56-
まずは子コンポーネント側で `defineProps` マクロを使用し、受け取りたいデータを定義します。
57-
58-
```vue
59-
<!-- Child.vue -->
60-
<script setup lang="ts">
61-
defineProps<{ message: string }>()
62-
</script>
63-
```
64-
65-
次に親コンポーネント側で、子コンポーネントにデータを渡すために `v-bind` ディレクティブを使用します。\
66-
`:props名="データ"` という形式で、子コンポーネントにデータを渡すことができます。
67-
68-
```vue
69-
<!-- Parent.vue -->
70-
<template>
71-
<Child :message="message" />
72-
</template>
73-
```
74-
75-
また、props 名とデータの変数名が同名の場合は省略記法を使うことができます。
76-
77-
```vue
78-
<!-- Parent.vue -->
79-
<template>
80-
<Child :message />
81-
</template>
82-
```
83-
84-
### Emit
85-
86-
子コンポーネントから親コンポーネントにイベントを発火するための方法です。
87-
88-
まずは子コンポーネント側で `defineEmits` マクロを使用し、発火したいイベントを定義します。
89-
emit 関数を用いて、イベントを発火することができます。
90-
91-
```vue
92-
<!-- Child.vue -->
93-
<script setup lang="ts">
94-
const emit = defineEmits<{ sendMessage: [] }>()
95-
</script>
96-
97-
<template>
98-
<button type="button" @click="emit('sendMessage')">
99-
Click me
100-
</button>
101-
</template>
102-
```
103-
104-
発火されたイベントは親コンポーネント側で `v-on` ディレクティブを使用して受け取ることができます。
105-
106-
```vue
107-
<!-- Parent.vue -->
108-
<script setup lang="ts">
109-
function handleSendMessage() {
110-
console.log('Message sent!')
111-
}
112-
</script>
113-
114-
<template>
115-
<Child @send-message="handleSendMessage" />
116-
</template>
117-
```
118-
119-
以下のように、イベント発火時に子コンポーネントからデータを受け渡すこともできます。
120-
121-
```vue
122-
<!-- Child.vue -->
123-
<script setup lang="ts">
124-
const emit = defineEmits<{ sendMessage: [string] }>()
125-
</script>
126-
127-
<template>
128-
<button type="button" @click="emit('sendMessage', 'Hello, Vue!')">
129-
Click me
130-
</button>
131-
</template>
132-
```
133-
134-
```vue
135-
<!-- Parent.vue -->
136-
<script setup lang="ts">
137-
function handleSendMessage(message: string) {
138-
console.log(message) // Hello, Vue!
139-
}
140-
</script>
141-
142-
<template>
143-
<Child @send-message="handleSendMessage" />
144-
</template>
145-
```
146-
147-
それぞれの詳しい [API ドキュメント](https://ja.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)から確認することができます。
148-
149-
## 現在の実装の課題
150-
151-
app.vueに定義されたtodosにTodoList.vueからアクセスすることができていません。
152-
`props``emit` を使用して、コンポーネント間でデータをやり取りできるようにしましょう。
153-
154-
## チャレンジ2
155-
156-
`props``emit` を使用して親子間でデータのやり取りをできるようにしましょう:
157-
158-
1. `TodoList.vue``defineProps`を使用して親から`todos`を受け取れるようにしましょう
159-
2. `app.vue``<TodoList />`を配置して、todosを渡してみましょう。
160-
3. `TodoList.vue``defineEmits`を使用してアイコンのクリックイベントを親に伝えられるようにしましょう
161-
4. `app.vue``TodoList.vue`から受け取ったイベントを利用して`updateDone`を実行しましょう
162-
163-
参考実装:
164-
165-
```vue
166-
<script setup lang="ts">
167-
/**
168-
* Props
169-
*/
170-
defineProps<{
171-
todos: Todo[]
172-
}>()
173-
174-
/*
175-
* Emit
176-
*/
177-
const emit = defineEmits<{
178-
updateDone: [number, boolean]
179-
}>()
180-
</script>
181-
```
182-
183-
## 実装後の効果
184-
185-
コンポーネント化すると:
186-
187-
- 関心ごと(表示とロジック)が分割され、コードの見通しが良くなる
188-
- 複数のコンポーネントで同じUIパーツを再利用できるようになる
189-
- 親子間のデータ受け渡し(props・emit)を通じて状態管理が整理され、チーム開発や拡張がしやすくなる
43+
3. `app.vue`で、`components/TodoList.vue``を`Todolist`として`import`し、`<template>`内に`<Todolist />`を作成しましょう。
19044

19145
もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。
19246

19347
:ButtonShowSolution
19448

195-
コンポーネント化され、メンテナンスしやすいスッキリした構造になりました!
49+
app.vueがすっきりとしました。
50+
51+
ただこのままでは、TodoList.vue側でapp.vueに定義された値にアクセスすることができません。
52+
次の章で、コンポーネント間のデータの受け渡し方を実装します。
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
import TodoList from './components/TodoList.vue'
4+
5+
/**
6+
* Data
7+
*/
8+
const todos = ref<Todo[]>([
9+
{
10+
id: 1,
11+
done: false,
12+
title: 'Vue Fes Japan 2025のチケット販売開始の宣伝をする',
13+
note: 'XとBlueskyで宣伝する。\n会社のslackでも宣伝する。',
14+
dueDate: '2025-10-24',
15+
},
16+
{
17+
id: 2,
18+
done: true,
19+
title: 'Vue Fes Japan ボランティアスタッフに応募する',
20+
note: '',
21+
dueDate: '',
22+
},
23+
])
24+
25+
/**
26+
* Methods
27+
*/
28+
function updateDone(id: number, done: boolean) {
29+
const targetTodo = todos.value.find(todo => todo.id === id)
30+
31+
if (targetTodo) {
32+
targetTodo.done = done
33+
}
34+
}
35+
36+
/**
37+
* Type
38+
*/
39+
interface Todo {
40+
id: number
41+
done: boolean
42+
title: string
43+
note: string
44+
dueDate: string
45+
}
46+
</script>
47+
48+
<template>
49+
<div class="container">
50+
<header class="header">
51+
<div class="header-left">
52+
<h1>Vue TODO Application</h1>
53+
</div>
54+
<div class="header-right">
55+
👤
56+
<span>Vue Fes Japan</span>
57+
</div>
58+
</header>
59+
60+
<main>
61+
<!-- TODO: TodoListコンポーネントにtodosをpropsとして渡す -->
62+
<TodoList />
63+
</main>
64+
65+
<footer class="footer">
66+
<p>Vue Fes Tokyo 2025</p>
67+
</footer>
68+
</div>
69+
</template>
70+
71+
<style scoped>
72+
.container {
73+
padding: 1rem 0 2.5rem;
74+
display: flex;
75+
flex-direction: column;
76+
gap: 2.5rem;
77+
min-height: 100vh;
78+
}
79+
80+
/* ------- header start ------- */
81+
.header {
82+
display: grid;
83+
grid-template-columns: 1fr auto;
84+
gap: 0.25rem;
85+
align-items: flex-end;
86+
}
87+
88+
.header-right {
89+
display: grid;
90+
grid-auto-flow: column;
91+
align-items: center;
92+
gap: 0.25rem;
93+
}
94+
95+
.header h1 {
96+
font-size: 1.5rem;
97+
font-weight: bold;
98+
}
99+
100+
.header img {
101+
width: 1.5rem;
102+
height: 1.5rem;
103+
}
104+
105+
.header span {
106+
font-size: 0.875rem;
107+
}
108+
/* ------- header last ------- */
109+
110+
/* footer */
111+
.footer {
112+
text-align: center;
113+
color: #666;
114+
}
115+
</style>

0 commit comments

Comments
 (0)