在 Vue 中,插槽(slot)是实现组件内容分发的机制,允许我们将子组件的内容传递给父组件,从而提升组件的可复用性和灵活性。插槽的本质是通过将父组件内容传递到子组件指定的插槽位置,使得子组件在渲染时可以动态填充不同的内容。
1. 插槽的类型
Vue 中有几种主要的插槽类型:
1、默认插槽(Default Slot)
默认插槽用于在组件没有指定插槽时,将内容插入到子组件的默认位置。
????:
<!-- 父组件中传递内容 -->
<MyComponent>
<p>这是默认插槽内容</p>
</MyComponent>
<!-- 子组件模板 -->
<template>
<div>
<slot></slot>
</div>
</template>
2、具名插槽(Named Slot)
具名插槽允许我们通过 name 属性指定插槽名称,以便在父组件中为不同的插槽位置传递内容:
????:
<!-- 父组件中传递具名内容 -->
<MyComponent>
<template v-slot:header>
<h1>标题内容</h1>
</template>
<template v-slot:footer>
<p>底部内容</p>
</template>
</MyComponent>
<!-- 子组件模板 -->
<template>
<div>
<slot name="header"></slot>
<!-- 默认插槽 -->
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
3、作用域插槽(Scoped Slot)
作用域插槽允许子组件向父组件提供数据,从而让父组件可以根据该数据动态渲染插槽内容。适用于一些内容需要根据子组件提供的数据进行渲染的场景。
????:
<!-- 父组件 -->
<MyComponent v-slot:default="{ data }">
<p>子组件提供的数据:{{ data }}</p>
</MyComponent>
<!-- 子组件模板 -->
<template>
<slot :data="someData"></slot>
</template>
<script>
export default {
data() {
return {
someData: 'Hello slot'
}
}
}
</script>
2. 插槽的本质
插槽的本质是将父组件内容通过编译成 vnode(虚拟节点),然后在子组件渲染时将这些 vnode 放置到子组件的指定位置。Vue 通过 vnode 的编译与插槽的传递,将插槽内容与子组件进行动态绑定,这样可以提升代码复用性,并保持组件内部逻辑的独立性。
当我们使用插槽传递数据时,实际传递的是一个对象 { },每一个插槽对应对象的属性。属性名:插槽名,默认为 default,属性值:函数 function,返回值是虚拟节点。
{
// 默认插槽
default: function(){},
// 具名插槽
slot1: function(){},
// 作用域插槽
slot2: function({ msg}){},
}
因此,传递插槽也就是在传递函数,使用插槽在调用函数,返回虚拟节点。
3. 验证结论
正常使用如下:
App.vue
<template>
<Comp>
<p>默认插槽:default slot</p>
<template #slot1>
<p>具名插槽:slot1</p>
</template>
<template #slot2="{ msg }">
<p>作用域插槽:{{ msg }}</p>
</template>
</Comp>
</template>
<script setup>
import Comp from './components/Comp.vue'
</script>
Comp.vue
<template>
<div>
<slot></slot>
<slot name="slot1"></slot>
<slot name="slot2" msg="hello slot"></slot>
</div>
</template>
展示为:
下面使用 JS 实现一下:
Comp.js
import { createElementVNode } from 'vue'
export default {
setup(_, { slots }) {
console.log('~ slots:', slots)
const defaultVNodes = slots.default()
const slot1VNodes = slots.slot1()
const slot2VNodes = slots.slot2({ msg: 'hello slot' })
console.log('~ VNodes', defaultVNodes, slot1VNodes, slot2VNodes)
return () => {
return createElementVNode('div', null, [...defaultVNodes, ...slot1VNodes, ...slot2VNodes])
}
}
}
或者
import { defineComponent, h } from 'vue'
export default defineComponent({
name: 'Comp',
props: {},
setup(props, { slots }) {
return () =>
h('div', [
slots.default ? slots.default() : h('p', '默认插槽'),
slots.slot1 ? slots.slot1() : h('p', '具名插槽 slot1'),
slots.slot2 ? slots.slot2({ msg: 'Hello scoped slot' }) : null
])
}
})
插槽的本质就是在子组件中调用函数,创建虚拟节点显示到页面上。
4. 使用 slot 注意事项
1、避免滥用插槽
插槽虽然可以极大地提升组件的灵活性,但过多的插槽会使组件的 API 变得复杂,不易理解和维护。
2、作用域插槽的命名
为了代码可读性,使用作用域插槽时建议使用有意义的命名,如 v-slot:default="{ item }" 而不是 v-slot="{ data }",这样能让代码的含义更加清晰。
3、默认插槽内容
在设计组件时,可以为插槽设置默认内容,以便在父组件未传递内容时,插槽依旧可以展示一些基本信息。比如:<slot>默认内容</slot>。
4、避免嵌套过深的插槽结构
深层嵌套的插槽结构可能会导致代码可读性差,同时也会增加渲染的复杂度,因此在设计组件时尽量保持插槽的扁平结构。