Vue 中的插槽(Slots)是 Vue 组件化开发中一个非常强大的功能,它允许你定义可复用的模板,并在使用组件时向其中注入自定义内容。
文章目录
1. 为什么需要插槽?
想象一个按钮组件:
<!-- Button.vue -->
<template>
<button class="my-btn">
<!-- 这里的内容应该由使用组件的人决定 -->
</button>
</template>
如果没有插槽,你可能会用 props 来传递文本:
<Button text="Click me"></Button>
但这限制了只能传递文本,无法传递更复杂的 HTML 结构(如图标、样式等)。插槽就是为了解决这个问题而生的,它允许父组件向子组件传递任意模板内容。
2. 基本插槽 (Default Slot)
最基本的插槽,也称为默认插槽。
子组件中定义插槽 (Child.vue)
使用 <slot> 标签作为占位符。
<template>
<div class="child-component">
<h2>我是子组件的标题</h2>
<!-- 插槽占位符 -->
<slot></slot>
<p>我是子组件的尾部</p>
</div>
</template>
父组件中使用 (Parent.vue)
在组件标签内部写入的内容,会替换子组件中的 <slot></slot>。
<template>
<Child>
<!-- 这部分内容会被注入到子组件的插槽中 -->
<p>这是父组件传递过来的内容!</p>
<button>一个按钮</button>
</Child>
</template>
最终渲染结果:
<div class="child-component">
<h2>我是子组件的标题</h2>
<!-- 插槽被替换为 -->
<p>这是父组件传递过来的内容!</p>
<button>一个按钮</button>
<p>我是子组件的尾部</p>
</div>
3. 后备内容 (Fallback Content)
你可以为插槽提供默认内容(后备内容)。如果父组件没有提供任何内容,后备内容就会显示。
子组件 (Child.vue)
<template>
<button class="my-btn">
<!-- 如果父组件没有提供内容,则显示“Submit” -->
<slot>Submit</slot>
</button>
</template>
父组件 (Parent.vue)
<!-- 使用后备内容 -->
<Child></Child>
<!-- 渲染结果: <button class="my-btn">Submit</button> -->
<!-- 提供自定义内容 -->
<Child>Click Me!</Child>
<!-- 渲染结果: <button class="my-btn">Click Me!</button> -->
4. 具名插槽 (Named Slots)
当一个组件需要多个插槽时,就需要使用具名插槽来区分它们。
子组件 (Card.vue)
使用 name 属性给每个 <slot> 命名。不带 name 的 <slot> 是默认插槽。
<template>
<div class="card">
<div class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<!-- 默认插槽 -->
<slot></slot>
</div>
<div class="card-footer">
<slot name="footer"></slot>
</div>
</div>
</template>
父组件 (Parent.vue)
使用 v-slot 指令(可简写为 #)在 <template> 上指定要插入哪个具名插槽。
Vue 2.6.0+ 语法 (推荐)
<template>
<Card>
<!-- 插入 header 插槽 -->
<template v-slot:header>
<h2>自定义标题</h2>
</template>
<!-- 插入默认插槽 (也可以不用 <template> 包裹) -->
<p>这是卡片的主体内容...</p>
<!-- 插入 footer 插槽 -->
<template #footer> <!-- # 是 v-slot: 的简写 -->
<button>保存</button>
</template>
</Card>
</template>
5. 作用域插槽 (Scoped Slots)
这是插槽最强大的功能。它允许子组件在插槽中向父组件传递数据,让父组件可以自定义如何渲染这些数据。
子组件 (TodoList.vue)
在 <slot> 上绑定属性(:todos="todos"),这些属性被称为插槽 Props。
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
<!-- 将 todo 对象作为插槽 Prop 传递出去 -->
<slot :todo="todo"></slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
todos: [
{ id: 1, text: 'Learn Vue', isComplete: true },
{ id: 2, text: 'Build something awesome', isComplete: false }
]
}
}
}
</script>
父组件 (Parent.vue)
使用 v-slot 来接收子组件传递过来的数据。可以使用解构语法。
<template>
<TodoList>
<!-- 接收插槽 Props,命名为 slotProps -->
<template v-slot:default="slotProps">
<!-- 现在可以自由决定如何渲染每个 todo -->
<span :style="{ textDecoration: slotProps.todo.isComplete ? 'line-through' : 'none' }">
{{ slotProps.todo.text }}
</span>
</template>
</TodoList>
</template>
使用解构语法更简洁:
<TodoList v-slot="{ todo }">
<span :style="{ textDecoration: todo.isComplete ? 'line-through' : 'none' }">
{{ todo.text }}
</span>
</TodoList>
6. 具名作用域插槽 (Named Scoped Slots)
结合具名插槽和作用域插槽。
子组件 (DataTable.vue)
<template>
<table>
<thead>
<tr>
<th>Header</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.id">
<!-- 为每个具名插槽传递数据 -->
<td><slot name="cell" :item="item"></slot></td>
</tr>
</tbody>
</table>
</template>
父组件 (Parent.vue)
<template>
<DataTable :items="userList">
<template #cell="{ item }">
<!-- 可以访问到子组件传来的 item -->
{{ item.name }} - {{ item.email }}
</template>
</DataTable>
</template>
总结与对比
| 插槽类型 | 关键字/用法 | 目的 |
|---|---|---|
| 默认插槽 | <slot> | 接收未包裹在 <template> 中的内容。 |
| 具名插槽 | v-slot:name 或 #name | 将内容分发到指定名称的插槽。 |
| 作用域插槽 | v-slot="data" | 子组件向父组件传递数据,父组件决定渲染样式。 |
| 后备内容 | <slot>默认内容</slot> | 父组件未提供内容时显示的默认值。 |
版本注意:
v-slot指令在 Vue 2.6.0+ 中引入,取代了旧的slot和slot-scope特性(现已废弃)。- 在 Vue 3 中,
v-slot是唯一推荐的用法。
简写规则:
v-slot:default可以简写为v-slotv-slot:header可以简写为#header
插槽极大地提高了组件的灵活性和复用性,尤其是在构建通用组件(如模态框、卡片、列表、表格)时,它是不可或缺的工具。
1503

被折叠的 条评论
为什么被折叠?



