作用域插槽实现
📖 本节总结
作用域插槽允许插槽内容访问子组件的数据,核心是通过 props 将数据从子组件传递到父组件。
什么是作用域插槽?
vue
<!-- 子组件 -->
<template>
<slot :user="user" :items="items"></slot>
</template>
<!-- 父组件 -->
<Child>
<template #default="{ user, items }">
<div>{{ user.name }} - {{ items.length }} 项</div>
</template>
</Child>1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
编译后
javascript
// 子组件的 render 函数
function render(_ctx) {
return h('div', [
renderSlot(_ctx.$slots, 'default', {
user: _ctx.user,
items: _ctx.items
})
])
}
// 父组件传入的插槽
const children = {
default: (props) => [h('div', `${props.user.name} - ${props.items.length} 项`)]
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
renderSlot 实现
javascript
// packages/runtime-core/src/helpers/renderSlot.ts
export function renderSlot(
slots,
name,
props,
fallback
) {
// 获取插槽函数
let slot = slots[name]
// 执行插槽函数,传入 props
// 这就是作用域插槽的核心:插槽内容可以访问子组件的数据
const slotContent = slot && slot(props)
return slotContent || (fallback ? fallback() : null)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
具名作用域插槽
vue
<!-- 子组件 -->
<template>
<div>
<slot name="item" v-for="item in items" :key="item.id" :item="item">
<!-- 默认内容 -->
<span>{{ item.name }}</span>
</slot>
</div>
</template>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
编译后
javascript
function render(_ctx) {
return h('div', [
..._ctx.items.map(item =>
renderSlot(_ctx.$slots, 'item', { item }, () => [
h('span', item.name)
])
)
])
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
动态作用域插槽
vue
<!-- 父组件 -->
<template #[dynamicSlot]="{ data }">
<p>{{ data }}</p>
</template>
<!-- 编译后 -->
h(Child, null, {
[dynamicSlot]: (props) => [h('p', props.data)]
})1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
useSlots 组合式 API
javascript
// 子组件中使用
import { useSlots } from 'vue'
export default {
setup() {
const slots = useSlots()
// 检查插槽是否存在
console.log('has header:', !!slots.header)
console.log('has default:', !!slots.default)
// 手动渲染插槽
if (slots.header) {
return () => slots.header()
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
$slots vs renderSlot
javascript
// $slots 是组件实例上的属性
console.log(vm.$slots) // { header: fn, default: fn, ... }
// renderSlot 是用于在 render 函数中渲染插槽的辅助函数
function render() {
return h('div', [
renderSlot(this.$slots, 'header'),
renderSlot(this.$slots, 'default')
])
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
插槽的更新
javascript
// 当子组件的数据变化时,会触发重新渲染
// 插槽内容也会重新执行
const state = reactive({ count: 0 })
// 子组件
function render() {
return h('div', [
// 每次 count 变化,这里会重新执行
renderSlot(this.$slots, 'default', { count: this.count })
])
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
插槽与 v-if/v-show
vue
<!-- 子组件 -->
<template>
<slot v-if="show"></slot>
</template>
<!-- 父组件 -->
<Child>
<!-- 这个内容只在 show 为 true 时渲染 -->
</Child>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
插槽与 v-for
vue
<!-- 子组件 -->
<template>
<div v-for="item in list" :key="item.id">
<slot :item="item"></slot>
</div>
</template>
<!-- 父组件 -->
<Child v-slot="{ item }">
<p>{{ item.name }}</p>
</Child>1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
总结
| 概念 | 说明 |
|---|---|
:item="item" | 向插槽传递数据 |
v-slot="{ item }" | 接收插槽数据 |
renderSlot(slots, name, props) | 渲染插槽函数 |
useSlots() | 组合式 API 获取插槽 |
| 作用域插槽 | 让插槽内容访问子组件数据 |