Vue3 插槽(Slots)用法总结
1. 默认插槽(Default Slots)
默认插槽是最基本的插槽类型,用于在组件中插入内容。
1.1 基本用法
<!-- BaseCard.vue -->
<template>
<div class="card">
<slot></slot> <!-- 默认插槽 -->
</div>
</template>
<!-- 使用组件 -->
<template>
<BaseCard>
<p>这是插入到默认插槽的内容</p>
</BaseCard>
</template>
1.2 带默认内容的插槽
<!-- BaseButton.vue -->
<template>
<button class="btn">
<slot>
点击按钮 <!-- 当没有提供内容时显示的默认内容 -->
</slot>
</button>
</template>
<!-- 使用组件 -->
<template>
<BaseButton>提交</BaseButton> <!-- 显示"提交" -->
<BaseButton /> <!-- 显示"点击按钮" -->
</template>
2. 具名插槽(Named Slots)
具名插槽允许我们在组件中定义多个插槽,并通过名字来区分。
2.1 基本用法
<!-- LayoutComponent.vue -->
<template>
<div class="layout">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<!-- 使用组件 -->
<template>
<LayoutComponent>
<template #header>
<h1>网站标题</h1>
</template>
<template #default>
<p>主要内容区域</p>
</template>
<template #footer>
<p>版权信息 © 2024</p>
</template>
</LayoutComponent>
</template>
2.2 动态插槽名
<!-- DynamicSlot.vue -->
<template>
<div class="container">
<slot :name="dynamicSlotName"></slot>
</div>
</template>
<script setup>
const dynamicSlotName = ref('content')
</script>
<!-- 使用组件 -->
<template>
<DynamicSlot>
<template #[dynamicSlotName]>
<p>动态插槽内容</p>
</template>
</DynamicSlot>
</template>
3. 作用域插槽(Scoped Slots)
作用域插槽允许子组件向插槽内容传递数据。
3.1 基本用法
<!-- ItemList.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="index">
<!-- 提供默认内容 -->
{{ item.name }}
</slot>
</li>
</ul>
</template>
<script setup>
const items = ref([
{ id: 1, name: '项目1', description: '描述1' },
{ id: 2, name: '项目2', description: '描述2' }
])
</script>
<!-- 使用组件 -->
<template>
<ItemList>
<template #default="{ item, index }">
<div class="item">
<h3>{{ index + 1 }}. {{ item.name }}</h3>
<p>{{ item.description }}</p>
</div>
</template>
</ItemList>
</template>
3.2 多个作用域插槽
<!-- DataTable.vue -->
<template>
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column.key">
<slot name="header" :column="column">
{{ column.title }}
</slot>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id">
<td v-for="column in columns" :key="column.key">
<slot :name="column.key" :row="row" :value="row[column.key]">
{{ row[column.key] }}
</slot>
</td>
</tr>
</tbody>
</table>
</template>
<!-- 使用组件 -->
<template>
<DataTable :columns="columns" :data="tableData">
<!-- 自定义表头 -->
<template #header="{ column }">
<strong>{{ column.title.toUpperCase() }}</strong>
</template>
<!-- 自定义状态列 -->
<template #status="{ value }">
<span :class="value">{{ value }}</span>
</template>
<!-- 自定义操作列 -->
<template #actions="{ row }">
<button @click="editRow(row)">编辑</button>
<button @click="deleteRow(row)">删除</button>
</template>
</DataTable>
</template>
4. 实际应用示例
4.1 可复用的模态框组件
<!-- Modal.vue -->
<template>
<div v-if="modelValue" class="modal">
<div class="modal-content">
<div class="modal-header">
<slot name="header">
<h3>默认标题</h3>
</slot>
<button @click="$emit('update:modelValue', false)">×</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="footer" :close="close">
<button @click="close">关闭</button>
</slot>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
modelValue: Boolean
})
const emit = defineEmits(['update:modelValue'])
const close = () => {
emit('update:modelValue', false)
}
</script>
<!-- 使用模态框 -->
<template>
<Modal v-model="showModal">
<template #header>
<h3>确认删除</h3>
</template>
<p>确定要删除这条记录吗?</p>
<template #footer="{ close }">
<button @click="handleDelete">确认</button>
<button @click="close">取消</button>
</template>
</Modal>
</template>
4.2 高级列表组件
<!-- ListView.vue -->
<template>
<div class="list-view">
<div class="list-header">
<slot name="header" :total="items.length">
<h2>列表 ({{ items.length }})</h2>
</slot>
</div>
<div class="list-filters">
<slot name="filters" :filters="filters" :updateFilter="updateFilter">
<!-- 默认过滤器UI -->
</slot>
</div>
<div class="list-content">
<template v-if="filteredItems.length">
<div v-for="item in filteredItems" :key="item.id" class="list-item">
<slot name="item" :item="item">
{{ item.name }}
</slot>
</div>
</template>
<template v-else>
<slot name="empty">
<p>暂无数据</p>
</slot>
</template>
</div>
<div class="list-footer">
<slot name="footer" :total="filteredItems.length"></slot>
</div>
</div>
</template>
5. 最佳实践
-
插槽命名规范
- 使用语义化的名称
- 保持命名一致性
- 使用小写字母和连字符
-
默认内容
- 为插槽提供合理的默认内容
- 确保组件在没有提供插槽内容时也能正常工作
-
作用域插槽数据
- 只传递必要的数据
- 使用清晰的属性名
- 考虑数据的响应性
-
性能考虑
- 避免在插槽中传递大量数据
- 合理使用缓存机制