1. この記事でやること
- 学習アウトプットとして、ボタンコンポーネントを props版→slot版 に置き換えて違いを確認する
2. 検証環境
- Vue 3.4.35
- Nuxt 3.12.4
- Node v22.20.0
- Yarn 1.22.18
- macOS Sequoia 15.6
3. 結論
- props:親は値(データ)を渡し、子が表示構造を決める
- slot:親がテンプレート内容(コンテンツ)を渡し、子は差し込み口(枠)を提供する
4. props版
ParentView.vue
<script setup>
import ChildButton from './components/ChildButton.vue'
</script>
<template>
<ChildButton label="保存" />
<ChildButton label="検索" icon="🔍" />
<ChildButton label="送信" icon="▶︎" comment="(必須)" />
</template>
ChildButton.vue
<script setup>
defineProps({
label: { type: String, required: true },
icon: { type: String, default: '' },
comment: { type: String, default: '' },
})
</script>
<template>
<button>
<span v-if="icon">{{ icon }}</span>
<span>{{ label }}</span>
<span v-if="comment">{{ comment }}</span>
</button>
</template>
- 親:
label / icon / commentを渡す - 子:v-if などで表示を制御する
- 良い点:呼び出し側がシンプルで、UIも統一しやすい
- 弱い点:パターンが増えるほど props が増え、条件分岐(v-if)も増えて可読性が下がりやすい(パターンが増えすぎるならslot検討のサイン)
5. slot版
ParentView.vue
<script setup>
import ChildButton from './components/ChildButton.vue'
</script>
<template>
<ChildButton>
<span>保存</span>
</ChildButton>
<ChildButton>
<span>🔍</span>
<span>検索</span>
</ChildButton>
<ChildButton>
<span>▶︎</span>
<span>送信</span>
<span>(必須)</span>
</ChildButton>
</template>
ChildButton.vue
<template>
<button>
<slot />
</button>
</template>
- 親:
<ChildButton>...</ChildButton>の中に構造を書く - 子:
<slot />で差し込む - 良い点:中身の自由度が高い(アイコン / 強調 / 複数要素など)
- 弱い点:呼び出し側が冗長になりやすく、書き方がバラつくとUIの統一が崩れやすい(名前付きslot等で統一しやすくする方法もある)