插槽是 Vue 组件系统中极其强大的功能,它允许组件在定义时预留"缺口",让使用组件的父级可以灵活地插入自定义内容。下面我将从基础到高级全面讲解 Vue 插槽的各个方面。
一、插槽的核心概念
1. 为什么需要插槽
假设我们要创建一个按钮组件:
vue
<!-- 简单按钮组件 -->
<template>
<button class="my-btn">按钮</button>
</template>
这个按钮的文本是固定的"按钮",如果想在不同地方使用时显示不同文本,常规做法是通过 props:
vue
<my-button text="提交"></my-button>
但当需要插入复杂内容(如图标+文字组合)时,props 就显得力不从心。这时就需要插槽。
2. 插槽的基本原理
插槽相当于组件内部的"占位符",父组件可以在使用子组件时,将任意内容"注入"到这些占位符中。
二、插槽类型详解
1. 默认插槽(匿名插槽)
基本用法
vue
<!-- 子组件 Child.vue -->
<template>
<div class="child">
<h3>子组件标题</h3>
<slot>默认内容(父组件不提供内容时显示)</slot>
</div>
</template>
<!-- 父组件使用 -->
<child>
<p>这是父组件插入的内容</p>
</child>
特点
- 一个组件可以有且只有一个默认插槽
- 当父组件不提供内容时,会显示
<slot>
标签内的默认内容 - 所有未包裹在
<template v-slot>
中的内容都会被视为默认插槽的内容
2. 具名插槽
当需要多个插槽时,可以为插槽命名:
vue
<!-- 子组件 Layout.vue -->
<template>
<div class="layout">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<!-- 父组件使用 -->
<layout>
<template v-slot:header>
<h1>页面标题</h1>
</template>
<p>这是主要内容...</p> <!-- 默认插槽内容 -->
<template v-slot:footer>
<p>© 2023 公司名称</p>
</template>
</layout>
具名插槽缩写语法
Vue 2.6.0+ 支持使用 #
代替 v-slot:
vue
<template #header>
<h1>页面标题</h1>
</template>
3. 作用域插槽
作用域插槽允许子组件将数据传递给插槽内容,实现"子传父"的数据流。
基本用法
vue
<!-- 子组件 UserList.vue -->
<template>
<ul>
<li v-for="user in users" :key="user.id">
<slot :user="user" :index="index"></slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
users: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
}
}
}
</script>
<!-- 父组件使用 -->
<user-list>
<template v-slot:default="slotProps">
<span>{{ slotProps.user.name }}</span> -
<span>序号: {{ slotProps.index }}</span>
</template>
</user-list>
解构语法
vue
<user-list>
<template v-slot:default="{ user, index }">
{{ index + 1 }}. {{ user.name }}
</template>
</user-list>
具名作用域插槽
vue
<!-- 子组件 -->
<slot name="item" :item="itemData" :index="itemIndex"></slot>
<!-- 父组件使用 -->
<template #item="{ item, index }">
<div>{{ index }} - {{ item.name }}</div>
</template>
三、高级插槽技巧
1. 动态插槽名
vue
<template>
<base-layout>
<template v-slot:[dynamicSlotName]>
动态插槽内容
</template>
</base-layout>
</template>
<script>
export default {
data() {
return {
dynamicSlotName: 'header' // 可以动态改变
}
}
}
</script>
2. 插槽组合使用
vue
<!-- 一个复杂的卡片组件 -->
<template>
<div class="card">
<div class="card-header">
<slot name="header">
<h3>{{ title }}</h3>
</slot>
</div>
<div class="card-body">
<slot></slot>
</div>
<div class="card-footer">
<slot name="footer">
<button @click="$emit('close')">关闭</button>
</slot>
</div>
</div>
</template>
3. 无渲染组件(Renderless Components)
利用作用域插槽创建只处理逻辑不渲染内容的组件:
vue
<!-- 鼠标位置追踪组件 -->
<template>
<slot :x="x" :y="y"></slot>
</template>
<script>
export default {
data() {
return {
x: 0,
y: 0
}
},
methods: {
updatePosition(e) {
this.x = e.clientX
this.y = e.clientY
}
},
mounted() {
window.addEventListener('mousemove', this.updatePosition)
},
beforeDestroy() {
window.removeEventListener('mousemove', this.updatePosition)
}
}
</script>
<!-- 使用 -->
<mouse-tracker v-slot="{ x, y }">
鼠标位置: {{ x }}, {{ y }}
</mouse-tracker>
四、插槽的底层原理
1. slots和slots和scopedSlots
-
this.$slots
: 访问静态插槽内容javascript// 检查默认插槽是否有内容 if (this.$slots.default) { // ... }
-
this.$scopedSlots
: 访问作用域插槽函数javascript// 调用作用域插槽并传递数据 const slotContent = this.$scopedSlots.default({ user: this.userData })
2. 渲染函数中的插槽
在渲染函数中直接操作插槽:
javascript
export default {
render(h) {
return h('div', [
// 默认插槽内容
this.$slots.default,
// 具名插槽
this.$slots.header,
// 作用域插槽
this.$scopedSlots.footer({
text: '页脚文本'
})
])
}
}
五、插槽的最佳实践
-
命名规范 :使用 kebab-case 命名具名插槽,如
header-left
-
合理使用默认内容:
vue<slot name="actions"> <button @click="$emit('cancel')">取消</button> <button @click="$emit('submit')">提交</button> </slot>
-
避免插槽嵌套过深:超过3层插槽嵌套会降低可维护性
-
文档化插槽:使用 JSDoc 或专用文档工具记录插槽用途
javascript/** * @slot header - 卡片头部内容 * @slot default - 卡片主要内容 * @slot footer - 卡片页脚,接收 { close } 参数 */
-
性能考虑 :作用域插槽会在父组件重新渲染时触发更新,必要时使用
v-once
六、Vue 3 中的插槽变化
-
统一
$slots
和$scopedSlots
:Vue 3 中只有$slots
,所有插槽都是函数 -
v-slot 简写 :可以更简洁地使用
#
符号vue<template #item="{ data }"> {{ data.name }} </template>
-
片段支持:组件可以有多个根节点,插槽内容可以分布在多个位置
-
组合式 API 中的插槽:
javascriptimport { useSlots } from 'vue' export default { setup() { const slots = useSlots() // 检查插槽是否存在 const hasFooter = !!slots.footer return { hasFooter } } }
通过深入理解和灵活运用插槽,你可以创建出高度灵活、可复用的 Vue 组件,大幅提高开发效率和代码质量。