🎯
Vue3のスロット機能を完全理解!基本から応用まで徹底解説
Vue3のスロット機能を完全理解!基本から応用まで徹底解説
Vue3のスロット(Slot)機能は、コンポーネントの再利用性と柔軟性を大幅に向上させる重要な機能です。この記事では、スロットの基本概念から実践的な応用パターンまで、段階的に詳しく解説していきます。
スロットとは
スロットは、親コンポーネントから子コンポーネントにコンテンツを渡すための仕組みです。HTMLの要素のように、コンポーネントの開始タグと終了タグの間にコンテンツを記述し、子コンポーネント内で<slot>要素を使って表示します。
基本的なスロットの例
<!-- ChildComponent.vue -->
<template>
<div class="card">
<h3>カードタイトル</h3>
<slot></slot>
</div>
</template>
<!-- ParentComponent.vue -->
<template>
<ChildComponent>
<p>これはスロットに渡されるコンテンツです</p>
</ChildComponent>
</template>
結果:
<div class="card">
<h3>カードタイトル</h3>
<p>これはスロットに渡されるコンテンツです</p>
</div>
デフォルトスロット
最も基本的なスロットがデフォルトスロットです。<slot>要素に名前を指定しない場合、デフォルトスロットとして扱われます。
実践例:モーダルコンポーネント
<!-- Modal.vue -->
<template>
<div v-if="isOpen" class="modal-overlay" @click="close">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h2>{{ title }}</h2>
<button @click="close" class="close-btn">×</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button @click="close">閉じる</button>
</slot>
</div>
</div>
</div>
</template>
<script setup>
defineProps({
isOpen: Boolean,
title: String
})
const emit = defineEmits(['close'])
const close = () => {
emit('close')
}
</script>
使用例:
<template>
<Modal :is-open="showModal" title="確認" @close="showModal = false">
<p>本当に削除しますか?</p>
<template #footer>
<button @click="deleteItem">削除</button>
<button @click="showModal = false">キャンセル</button>
</template>
</Modal>
</template>
名前付きスロット
複数のスロットを使用する場合は、名前付きスロットを使用します。<slot name="slotName">で定義し、親コンポーネントでは<template #slotName>で指定します。
実践例:レイアウトコンポーネント
<!-- Layout.vue -->
<template>
<div class="layout">
<header class="header">
<slot name="header"></slot>
</header>
<main class="main">
<slot></slot>
</main>
<aside class="sidebar">
<slot name="sidebar"></slot>
</aside>
<footer class="footer">
<slot name="footer"></slot>
</footer>
</div>
</template>
使用例:
<template>
<Layout>
<template #header>
<h1>サイトタイトル</h1>
<nav>ナビゲーション</nav>
</template>
<p>メインコンテンツ</p>
<template #sidebar>
<div>サイドバーコンテンツ</div>
</template>
<template #footer>
<p>© 2024 My Site</p>
</template>
</Layout>
</template>
スコープ付きスロット
スコープ付きスロットは、子コンポーネントから親コンポーネントにデータを渡すことができる高度な機能です。
基本的なスコープ付きスロット
<!-- UserList.vue -->
<template>
<div class="user-list">
<div v-for="user in users" :key="user.id" class="user-item">
<slot :user="user" :index="index"></slot>
</div>
</div>
</template>
<script setup>
defineProps({
users: Array
})
</script>
使用例:
<template>
<UserList :users="users">
<template #default="{ user, index }">
<div class="user-card">
<img :src="user.avatar" :alt="user.name">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<span class="user-number">#{{ index + 1 }}</span>
</div>
</template>
</UserList>
</template>
実践例:データテーブルコンポーネント
<!-- DataTable.vue -->
<template>
<div class="data-table">
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column.key">
{{ column.label }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in data" :key="item.id">
<td v-for="column in columns" :key="column.key">
<slot
:name="column.key"
:item="item"
:index="index"
:value="item[column.key]"
>
{{ item[column.key] }}
</slot>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
defineProps({
data: Array,
columns: Array
})
</script>
使用例:
<template>
<DataTable :data="users" :columns="columns">
<template #avatar="{ item }">
<img :src="item.avatar" :alt="item.name" class="avatar">
</template>
<template #status="{ value }">
<span :class="['status', `status--${value}`]">
{{ value }}
</span>
</template>
<template #actions="{ item }">
<button @click="editUser(item)">編集</button>
<button @click="deleteUser(item)">削除</button>
</template>
</DataTable>
</template>
<script setup>
const users = ref([
{ id: 1, name: '田中太郎', email: 'tanaka@example.com', avatar: '/avatar1.jpg', status: 'active' },
{ id: 2, name: '佐藤花子', email: 'sato@example.com', avatar: '/avatar2.jpg', status: 'inactive' }
])
const columns = [
{ key: 'avatar', label: 'アバター' },
{ key: 'name', label: '名前' },
{ key: 'email', label: 'メール' },
{ key: 'status', label: 'ステータス' },
{ key: 'actions', label: '操作' }
]
</script>
動的スロット
動的スロットを使用すると、実行時にスロット名を決定できます。
<!-- DynamicSlot.vue -->
<template>
<div class="dynamic-slot">
<slot :name="slotName" :data="data"></slot>
</div>
</template>
<script setup>
defineProps({
slotName: String,
data: Object
})
</script>
使用例:
<template>
<DynamicSlot :slot-name="currentView" :data="userData">
<template #profile="{ data }">
<div>プロフィール: {{ data.name }}</div>
</template>
<template #settings="{ data }">
<div>設定: {{ data.email }}</div>
</template>
</DynamicSlot>
</template>
<script setup>
const currentView = ref('profile')
const userData = ref({ name: '田中太郎', email: 'tanaka@example.com' })
</script>
条件付きスロット
条件に応じてスロットの内容を表示する方法です。
<!-- ConditionalSlot.vue -->
<template>
<div class="conditional-slot">
<slot v-if="showContent" name="content"></slot>
<slot v-else name="loading"></slot>
</div>
</template>
<script setup>
defineProps({
showContent: Boolean
})
</script>
使用例:
<template>
<ConditionalSlot :show-content="!loading">
<template #content>
<div>データが読み込まれました</div>
</template>
<template #loading>
<div class="spinner">読み込み中...</div>
</template>
</ConditionalSlot>
</template>
スロットのフォールバック
スロットにフォールバックコンテンツを設定できます。親コンポーネントからコンテンツが渡されない場合に表示されます。
<!-- Button.vue -->
<template>
<button class="btn" :class="variant">
<slot>
<span>デフォルトボタン</span>
</slot>
</button>
</template>
<script setup>
defineProps({
variant: {
type: String,
default: 'primary'
}
})
</script>
使用例:
<!-- フォールバックコンテンツが表示される -->
<Button variant="secondary" />
<!-- カスタムコンテンツが表示される -->
<Button variant="danger">
<i class="icon-delete"></i>
削除
</Button>
実践的な応用パターン
1. カードコンポーネント
<!-- Card.vue -->
<template>
<div class="card" :class="cardClass">
<div v-if="$slots.header" class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<slot></slot>
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script setup>
const props = defineProps({
variant: {
type: String,
default: 'default',
validator: (value) => ['default', 'primary', 'success', 'warning', 'danger'].includes(value)
},
shadow: {
type: Boolean,
default: false
}
})
const cardClass = computed(() => [
`card--${props.variant}`,
{ 'card--shadow': props.shadow }
])
</script>
2. フォームフィールドコンポーネント
<!-- FormField.vue -->
<template>
<div class="form-field">
<label v-if="label" class="form-field__label">
{{ label }}
<span v-if="required" class="required">*</span>
</label>
<div class="form-field__input">
<slot :field="fieldProps" :error="error"></slot>
</div>
<div v-if="error" class="form-field__error">
{{ error }}
</div>
<div v-if="$slots.help" class="form-field__help">
<slot name="help"></slot>
</div>
</div>
</template>
<script setup>
const props = defineProps({
label: String,
required: Boolean,
error: String,
modelValue: [String, Number, Boolean, Array, Object]
})
const emit = defineEmits(['update:modelValue'])
const fieldProps = computed(() => ({
value: props.modelValue,
'onUpdate:modelValue': (value) => emit('update:modelValue', value)
}))
</script>
使用例:
<template>
<FormField
label="ユーザー名"
:required="true"
:error="errors.username"
v-model="username"
>
<template #default="{ field }">
<input
v-bind="field"
type="text"
placeholder="ユーザー名を入力"
class="form-input"
/>
</template>
<template #help>
<p>3文字以上20文字以下で入力してください</p>
</template>
</FormField>
</template>
スロットのベストプラクティス
1. 適切なスロット名の使用
<!-- 良い例:意味のある名前 -->
<slot name="header"></slot>
<slot name="content"></slot>
<slot name="footer"></slot>
<!-- 悪い例:意味のない名前 -->
<slot name="slot1"></slot>
<slot name="slot2"></slot>
2. スコープ付きスロットでの型安全性
<!-- UserCard.vue -->
<template>
<div class="user-card">
<slot :user="user" :isLoading="isLoading"></slot>
</div>
</template>
<script setup lang="ts">
interface User {
id: number
name: string
email: string
}
defineProps<{
user: User
isLoading: boolean
}>()
</script>
3. スロットの存在チェック
<template>
<div class="component">
<div v-if="$slots.header" class="header">
<slot name="header"></slot>
</div>
<div class="content">
<slot></slot>
</div>
<div v-if="$slots.footer" class="footer">
<slot name="footer"></slot>
</div>
</div>
</template>
まとめ
Vue3のスロット機能は、コンポーネントの再利用性と柔軟性を大幅に向上させる強力な機能です。この記事で紹介した内容をまとめると:
基本機能
- デフォルトスロット: 基本的なコンテンツの受け渡し
- 名前付きスロット: 複数のスロットを使った柔軟なレイアウト
- スコープ付きスロット: 子から親へのデータの受け渡し
応用機能
- 動的スロット: 実行時のスロット名決定
- 条件付きスロット: 条件に応じたスロット表示
- フォールバック: デフォルトコンテンツの設定
ベストプラクティス
- 意味のあるスロット名の使用
- 型安全性の確保
- スロットの存在チェック
これらの機能を適切に活用することで、保守性が高く、再利用可能なVue3コンポーネントを構築できます。スロットはVue3の重要な機能の一つなので、ぜひ実際のプロジェクトで活用してみてください!
Discussion