vue插槽进化
插槽分为普通插槽和具名插槽
两种插槽的目的都是让子组件 slot 占位符生成的内容由父组件来决定
# 一、普通插槽
父组件
<div class="container">
<header>
<slot name="header">
default
</slot>
</header>
</div>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
<template slot="header">
<div class="header">
header
</div>
</template>
1
2
3
4
5
6
2
3
4
5
6
# 二、具名插槽
# before vue 2.6.0
父组件
<div class="container">
<header>
<slot name="header" :info="info">
{{info.text}}
</slot>
</header>
</div>
1
2
3
4
5
6
7
2
3
4
5
6
7
子组件
<template slot="header" slot-scope="info">
<div class="header">
{{ info.text }}
</div>
</template>
1
2
3
4
5
2
3
4
5
# after vue 2.6.0
新增指令 v-slot
子组件
<template v-slot:avatar="info">
<div class="header">
{{ info.text }}
</div>
</template>
1
2
3
4
5
2
3
4
5
# 三、两种插槽的区别
before vue 2.6.0
# 普通插槽
在子组件编译和渲染阶段生成 vnodes,所以数据的作用域是父组件实例,子组件渲染的时候直接拿到这些渲染好的 vnodes
# 作用域插槽
父组件在编译和渲染阶段并不会直接生成 vnodes,而是在父节点 vnode 的 data 中保留一个 scopedSlots 对象,
存储着不同名称的插槽以及它们对应的渲染函数,只有在编译和渲染子组件阶段才会执行这个渲染函数生成 vnodes,
由于是在子组件环境执行的,所以对应的数据作用域是子组件实例。
由于生成 slot 的作用域是在父组件中,所以明明是子组件的插槽 slot 的更新是会带着父组件一起更新的
after vue 2.6.0
- slot 和 slot-scope 在组件内部被统一整合成了 函数
- 他们的渲染作用域都是 子组件
- 并且都能通过 this.$scopedSlots去访问
如果是 普通插槽,就直接调用函数生成 vnode,如果是 作用域插槽,
就直接带着 props 也就是 { msg } 去调用函数生成 vnode。 2.6 版本后统一为函数的插槽降低了理解难度。
Vue 2.6 也尽可能的让 slot 的更新不触发父组件的渲染,通过一系列巧妙的判断和算法去尽可能避免不必要的渲染
vue 2.6.* 更详细的代码分析 (opens new window)
# 四、Vue3中的slot渲染
Vue3中作者延续vue 2.6的优化
Vue3 source renderSlot.ts (opens new window)
export function renderSlot(
slots: Slots,
name: string,
props: Data = {},
// this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be a function returning an array
fallback?: () => VNodeArrayChildren,
noSlotted?: boolean
): VNode {
// 兼容插件方指定了某个vue版本, 和项目的版本不同,会报错的问题
if (currentRenderingInstance!.isCE) {
return createVNode(
'slot',
name === 'default' ? null : { name },
fallback && fallback()
)
}
let slot = slots[name]
if (__DEV__ && slot && slot.length > 1) {
warn(
`SSR-optimized slot function detected in a non-SSR-optimized render ` +
`function. You need to mark this component with $dynamic-slots in the ` +
`parent template.`
)
slot = () => []
}
// a compiled slot disables block tracking by default to avoid manual
// invocation interfering with template-based block tracking, but in
// `renderSlot` we can be sure that it's template-based so we can force
// enable it.
// 打上未编译标记
if (slot && (slot as ContextualRenderFn)._c) {
;(slot as ContextualRenderFn)._d = false
}
const validSlotContent = slot && ensureValidVNode(slot(props)) // 确保节点合法
const rendered = createBlock(
Fragment,
{ key: props.key || `_${name}` },
validSlotContent || (fallback ? fallback() : []),
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
? PatchFlags.STABLE_FRAGMENT
: PatchFlags.BAIL
)
if (!noSlotted && rendered.scopeId) {
// 给渲染后的对象加上slotScopeIds属性 在slot数据发生变化时,更新父组件和子组件时将参与update规则
rendered.slotScopeIds = [rendered.scopeId + '-s']
}
// 打上编译完成标记 避免重复编译
if (slot && (slot as ContextualRenderFn)._c) {
;(slot as ContextualRenderFn)._d = true
}
return rendered
}
export function createBlock(
type: VNodeTypes | ClassComponent,
props?: Record<string, any> | null,
children?: any,
patchFlag?: number,
dynamicProps?: string[]
): VNode {
return setupBlock(
createVNode(
type,
props,
children,
patchFlag,
dynamicProps,
true /* isBlock: prevent a block from tracking itself */
)
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
上次更新: 2023/04/05, 09:41:10