
Vue.js 親子コンポーネント 0から完全ガイド 子から親をマスターしよう編 #一部無料
1. そもそも「コンポーネント」って何?
🏗️ 日常生活で例えると
コンポーネント = 部品
家を建てる時...
🏠 家全体 ← これが「アプリケーション」
├── 🚪 ドア ← これが「コンポーネント」
├── 🪟 窓 ← これが「コンポーネント」
├── 🔥 キッチン ← これが「コンポーネント」
└── 🛏️ ベッドルーム ← これが「コンポーネント」
それぞれの部品(コンポーネント)は:
✅ 独立して作れる
✅ 何度でも使い回せる
✅ 他の部品と組み合わせられる
💻 ウェブサイトで例えると
ウェブサイト
├── ヘッダー(ロゴ、メニュー)
├── サイドバー(ナビゲーション)
├── メインコンテンツ
│ ├── 記事カード
│ ├── 記事カード
│ └── 記事カード
└── フッター(リンク、著作権)
Vue.js では、これらの「部品」がコンポーネントです!
2. 「親子関係」って何?
👨👩👧👦 家族で例えると
👨 お父さん(親)
├── 👧 娘(子)
├── 👦 息子(子)
└── 👶 赤ちゃん(子)
お父さんが子供たちに:
📢 「お小遣いをあげる」(親→子にデータを渡す)
👂 「子供の話を聞く」(子→親にメッセージを送る)
💻 Vue.js で例えると
📄 App.vue(親コンポーネント)
├── 📝 Header.vue(子コンポーネント)
├── 📝 MainContent.vue(子コンポーネント)
└── 📝 Footer.vue(子コンポーネント)
親が子に:
📤 データを渡す(プロパティ)
📥 子からの連絡を受け取る(イベント)
3. 一番簡単な例から始めよう
ステップ1: 子コンポーネントを作る
まず、一番シンプルな子コンポーネントを作りましょう。
<!-- ChildComponent.vue(子コンポーネント) -->
<template>
<div class="child">
<h2>私は子コンポーネントです!</h2>
<p>こんにちは!</p>
</div>
</template>
<script>
export default {
name: 'ChildComponent' // コンポーネントの名前
}
</script>
<style>
.child {
border: 2px solid blue;
padding: 10px;
margin: 10px;
}
</style>
解説:
<template>: HTMLの部分(見た目)
<script>: JavaScriptの部分(動作)
<style>: CSSの部分(スタイル)
ステップ2: 親コンポーネントで子を使う
<!-- ParentComponent.vue(親コンポーネント) -->
<template>
<div class="parent">
<h1>私は親コンポーネントです!</h1>
<!-- 子コンポーネントを使用 -->
<ChildComponent />
<ChildComponent />
<ChildComponent />
</div>
</template>
<script>
// 子コンポーネントをインポート(読み込み)
import ChildComponent from './ChildComponent.vue'
export default {
name: 'ParentComponent',
components: {
ChildComponent // 子コンポーネントを登録
}
}
</script>
<style>
.parent {
border: 2px solid red;
padding: 20px;
}
</style>
表示結果:
┌─────────────────────────────────────┐
│ 私は親コンポーネントです! │
│ ┌─────────────────────────────────┐ │
│ │ 私は子コンポーネントです! │ │
│ │ こんにちは! │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ 私は子コンポーネントです! │ │
│ │ こんにちは! │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ 私は子コンポーネントです! │ │
│ │ こんにちは! │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
4. データを親から子に渡してみよう(Props)
🎁 「プロパティ」って何?
プロパティ(Props)= 親から子への贈り物
👨 お父さん: 「娘ちゃん、お小遣い1000円だよ」
👧 娘: 「ありがとう!1000円もらった!」
Vue.js では:
👨 親コンポーネント: 「子よ、"Hello"というデータだよ」
👧 子コンポーネント: 「"Hello"を受け取りました!」
実際のコード例
子コンポーネント(データを受け取る側)
<!-- ChildComponent.vue -->
<template>
<div class="child">
<h2>私は子コンポーネントです!</h2>
<p>親からもらったメッセージ: {{ message }}</p>
<p>親からもらった数字: {{ number }}</p>
</div>
</template>
<script>
export default {
name: 'ChildComponent',
props: {
// 親から受け取るデータを定義
message: String, // 文字列型のデータ
number: Number // 数値型のデータ
}
}
</script>
解説:
props: 親から受け取るデータの設定
String, Number: データの型を指定
{{ message }}: テンプレート内でデータを表示
親コンポーネント(データを渡す側)
<!-- ParentComponent.vue -->
<template>
<div class="parent">
<h1>私は親コンポーネントです!</h1>
<!-- 子にデータを渡す -->
<ChildComponent
:message="greeting"
:number="favoriteNumber"
/>
<ChildComponent
:message="anotherGreeting"
:number="anotherNumber"
/>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
name: 'ParentComponent',
components: {
ChildComponent
},
data() {
return {
greeting: 'こんにちは!元気ですか?',
favoriteNumber: 42,
anotherGreeting: 'おはようございます!',
anotherNumber: 100
}
}
}
</script>
重要なポイント:
:message="greeting": :(コロン)を付けると動的なデータ
message="こんにちは": :なしだと固定の文字列
表示結果:
┌─────────────────────────────────────┐
│ 私は親コンポーネントです! │
│ ┌─────────────────────────────────┐ │
│ │ 私は子コンポーネントです! │ │
│ │ 親からもらったメッセージ: │ │
│ │ こんにちは!元気ですか? │ │
│ │ 親からもらった数字: 42 │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ 私は子コンポーネントです! │ │
│ │ 親からもらったメッセージ: │ │
│ │ おはようございます! │ │
│ │ 親からもらった数字: 100 │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
5. 子から親にメッセージを送ってみよう(Events)
📢 「イベント」って何?
イベント = 子から親への報告
👧 娘: 「お父さん、宿題終わったよ!」
👨 お父さん: 「よく頑張ったね!ご褒美をあげよう」
Vue.js では:
👧 子コンポーネント: 「親よ、ボタンが押されました!」
👨 親コンポーネント: 「了解!何か処理をしよう」
実際のコード例
子コンポーネント(メッセージを送る側)
<!-- ChildComponent.vue -->
<template>
<div class="child">
<h2>私は子コンポーネントです!</h2>
<p>親からもらったメッセージ: {{ message }}</p>
<!-- ボタンを押すと親にメッセージを送る -->
<button @click="sendMessageToParent">親にメッセージを送る</button>
<button @click="sendNumberToParent">親に数字を送る</button>
</div>
</template>
<script>
export default {
name: 'ChildComponent',
props: {
message: String
},
methods: {
sendMessageToParent() {
// 親に「hello-parent」というイベントとメッセージを送る
this.$emit('hello-parent', '子からのメッセージです!')
},
sendNumberToParent() {
// 親に「number-sent」というイベントと数字を送る
this.$emit('number-sent', 123)
}
}
}
</script>
解説:
this.$emit('イベント名', データ): 親にメッセージを送る
@click: ボタンがクリックされた時の処理
親コンポーネント(メッセージを受け取る側)
<!-- ParentComponent.vue -->
<template>
<div class="parent">
<h1>私は親コンポーネントです!</h1>
<p>子からのメッセージ: {{ childMessage }}</p>
<p>子からの数字: {{ childNumber }}</p>
<!-- 子からのイベントを受け取る -->
<ChildComponent
:message="greeting"
@hello-parent="receiveChildMessage"
@number-sent="receiveChildNumber"
/>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
name: 'ParentComponent',
components: {
ChildComponent
},
data() {
return {
greeting: 'こんにちは!',
childMessage: 'まだメッセージがありません',
childNumber: 0
}
},
methods: {
receiveChildMessage(message) {
// 子から受け取ったメッセージを保存
this.childMessage = message
console.log('子からメッセージが届きました:', message)
},
receiveChildNumber(number) {
// 子から受け取った数字を保存
this.childNumber = number
console.log('子から数字が届きました:', number)
}
}
}
</script>
重要なポイント:
@hello-parent="receiveChildMessage": @でイベントを受け取る
receiveChildMessage(message): メソッドの引数で子からのデータを受け取る
6. 実用的な例:カウンターアプリ
実際に動くアプリを作ってみましょう!
子コンポーネント(カウンターボタン)
<!-- CounterButton.vue -->
<template>
<div class="counter-button">
<h3>{{ title }}</h3>
<p>現在の値: {{ currentValue }}</p>
<button @click="increment">+1する</button>
<button @click="decrement">-1する</button>
<button @click="reset">リセット</button>
</div>
</template>
<script>
export default {
name: 'CounterButton',
props: {
title: String, // ボタンのタイトル
currentValue: Number // 現在の値
},
methods: {
increment() {
// 親に「値を増やして」と連絡
this.$emit('increment')
},
decrement() {
// 親に「値を減らして」と連絡
this.$emit('decrement')
},
reset() {
// 親に「値をリセットして」と連絡
this.$emit('reset')
}
}
}
</script>
<style>
.counter-button {
border: 1px solid #ccc ;
padding: 15px;
margin: 10px;
border-radius: 5px;
}
button {
margin: 5px;
padding: 5px 10px;
}
</style>
親コンポーネント(カウンター管理)
<!-- CounterApp.vue -->
<template>
<div class="counter-app">
<h1>カウンターアプリ</h1>
<p>合計: {{ total }}</p>
<!-- 複数のカウンターボタン -->
<CounterButton
title="カウンター1"
:currentValue="counter1"
@increment="incrementCounter1"
@decrement="decrementCounter1"
@reset="resetCounter1"
/>
<CounterButton
title="カウンター2"
:currentValue="counter2"
@increment="incrementCounter2"
@decrement="decrementCounter2"
@reset="resetCounter2"
/>
<CounterButton
title="カウンター3"
:currentValue="counter3"
@increment="incrementCounter3"
@decrement="decrementCounter3"
@reset="resetCounter3"
/>
<button @click="resetAll">全てリセット</button>
</div>
</template>
<script>
import CounterButton from './CounterButton.vue'
export default {
name: 'CounterApp',
components: {
CounterButton
},
data() {
return {
counter1: 0,
counter2: 0,
counter3: 0
}
},
computed: {
// 全カウンターの合計を計算
total() {
return this.counter1 + this.counter2 + this.counter3
}
},
methods: {
// カウンター1の操作
incrementCounter1() {
this.counter1++
},
decrementCounter1() {
this.counter1--
},
resetCounter1() {
this.counter1 = 0
},
// カウンター2の操作
incrementCounter2() {
this.counter2++
},
decrementCounter2() {
this.counter2--
},
resetCounter2() {
this.counter2 = 0
},
// カウンター3の操作
incrementCounter3() {
this.counter3++
},
decrementCounter3() {
this.counter3--
},
resetCounter3() {
this.counter3 = 0
},
// 全てリセット
resetAll() {
this.counter1 = 0
this.counter2 = 0
this.counter3 = 0
}
}
}
</script>
<style>
.counter-app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
</style>
7. もっと実用的な例:Todo リスト
子コンポーネント(Todo項目)
<!-- TodoItem.vue -->
<template>
<div class="todo-item" :class="{ completed: todo.completed }">
<input
type="checkbox"
:checked="todo.completed"
@change="toggleComplete"
/>
<span class="todo-text">{{ todo.text }}</span>
<button @click="deleteTodo" class="delete-btn">削除</button>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props: {
todo: Object // Todo項目のデータ
},
methods: {
toggleComplete() {
// 親に「完了状態を切り替えて」と連絡
this.$emit('toggle-complete', this.todo.id)
},
deleteTodo() {
// 親に「この項目を削除して」と連絡
this.$emit('delete-todo', this.todo.id)
}
}
}
</script>
<style>
.todo-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee ;
}
.todo-text {
flex: 1;
margin: 0 10px;
}
.completed .todo-text {
text-decoration: line-through;
color: #999 ;
}
.delete-btn {
background: #ff4444 ;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
}
</style>
親コンポーネント(Todo管理)
<!-- TodoApp.vue -->
<template>
<div class="todo-app">
<h1>Todoリスト</h1>
<!-- 新しいTodoを追加するフォーム -->
<div class="add-todo">
<input
v-model="newTodoText"
@keyup.enter="addTodo"
placeholder="新しいTodoを入力..."
/>
<button @click="addTodo">追加</button>
</div>
<!-- 統計情報 -->
<div class="stats">
<p>全体: {{ todos.length }}個</p>
<p>完了: {{ completedCount }}個</p>
<p>未完了: {{ remainingCount }}個</p>
</div>
<!-- Todo項目一覧 -->
<div class="todo-list">
<TodoItem
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@toggle-complete="toggleTodoComplete"
@delete-todo="deleteTodo"
/>
</div>
<!-- 一括操作ボタン -->
<div class="bulk-actions">
<button @click="markAllComplete">全て完了</button>
<button @click="deleteCompleted">完了済みを削除</button>
<button @click="deleteAll">全て削除</button>
</div>
</div>
</template>
<script>
import TodoItem from './TodoItem.vue'
export default {
name: 'TodoApp',
components: {
TodoItem
},
data() {
return {
newTodoText: '',
todos: [
{ id: 1, text: 'Vue.jsを学習する', completed: false },
{ id: 2, text: 'コンポーネントを理解する', completed: true },
{ id: 3, text: 'アプリを作ってみる', completed: false }
]
}
},
computed: {
completedCount() {
return this.todos.filter(todo => todo.completed).length
},
remainingCount() {
return this.todos.filter(todo => !todo.completed).length
}
},
methods: {
addTodo() {
if (this.newTodoText.trim()) {
const newTodo = {
id: Date.now(), // 簡単なID生成
text: this.newTodoText,
completed: false
}
this.todos.push(newTodo)
this.newTodoText = ''
}
},
toggleTodoComplete(todoId) {
const todo = this.todos.find(t => t.id === todoId)
if (todo) {
todo.completed = !todo.completed
}
},
deleteTodo(todoId) {
this.todos = this.todos.filter(t => t.id !== todoId)
},
markAllComplete() {
this.todos.forEach(todo => {
todo.completed = true
})
},
deleteCompleted() {
this.todos = this.todos.filter(todo => !todo.completed)
},
deleteAll() {
if (confirm('本当に全て削除しますか?')) {
this.todos = []
}
}
}
}
</script>
<style>
.todo-app {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.add-todo {
display: flex;
margin-bottom: 20px;
}
.add-todo input {
flex: 1;
padding: 10px;
border: 1px solid #ddd ;
border-radius: 4px;
}
.add-todo button {
padding: 10px 15px;
margin-left: 10px;
background: #007bff ;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.stats {
background: #f8f9fa ;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
}
.stats p {
margin: 5px 0;
}
.todo-list {
border: 1px solid #ddd ;
border-radius: 4px;
margin-bottom: 20px;
}
.bulk-actions button {
margin: 5px;
padding: 8px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.bulk-actions button:nth-child(1) { background: #28a745 ; color: white; }
.bulk-actions button:nth-child(2) { background: #ffc107 ; color: black; }
.bulk-actions button:nth-child(3) { background: #dc3545 ; color: white; }
</style>
8. まとめ:親子コンポーネントの重要ポイント
🎯 データの流れ
親コンポーネント
│
├── データ(data)
│ ↓ Props(:propName)
├── 子コンポーネント1
│ ↑ Events(@eventName)
├── メソッド(methods)
│
├── データ(data)
│ ↓ Props(:propName)
├── 子コンポーネント2
│ ↑ Events(@eventName)
└── メソッド(methods)
💡 覚えておくべき文法
<!-- 子コンポーネントの使用 -->
<ChildComponent
:prop1="parentData1" <!-- データを渡す(親→子) -->
:prop2="parentData2" <!-- データを渡す(親→子) -->
@event1="parentMethod1" <!-- イベントを受け取る(子→親) -->
@event2="parentMethod2" <!-- イベントを受け取る(子→親) -->
/>
// 子コンポーネント内
export default {
props: {
prop1: String, // 親からのデータを受け取る
prop2: Number
},
methods: {
someMethod() {
// 親にイベントを送る
this.$emit('event1', 'some data')
}
}
}
🚀 実践のコツ
小さく始める: まず簡単な例から
1つずつ理解: PropsとEventsを別々に学習
console.logを活用: データの流れを確認
実際に作ってみる: 手を動かして覚える
❌ よくある間違い
<!-- ❌ 間違い -->
<ChildComponent message="parentData" /> <!-- :が抜けている -->
<!-- ✅ 正しい -->
<ChildComponent :message="parentData" /> <!-- :を付ける -->
<!-- ❌ 間違い -->
<ChildComponent v-on:click="method" /> <!-- @を使おう -->
<!-- ✅ 正しい -->
<ChildComponent @click="method" /> <!-- @を使う -->
これで Vue.js の親子コンポーネントの基礎は完璧です!実際にコードを書いて試してみてくださいね!