0%

插槽的工作原理及实现

MyComponent组件模板:

1
2
3
4
5
6
7
<template>
<header><slot name="header"></slot></header>
<div>
<slot name="body"></slot>
</div>
<footer><slot name="footer"></slot></footer>
</template>

当在父组件中使用组件时,可以根据插槽名字插入自定义的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<MyComponent>
<template #header>
<h1>
我是标题
</h1>
</template>
<template #body>
<section>
我是内容
</section>
</template>
<template #footer>
<p>
我是注脚
</p>
</template>
</MyComponent>

父组件模板会被编译成如下渲染函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function render(){
return{
type: MyComponent,
//组件的children会被编译成一个对象
children: {
header() {
return {type:'h1',children:'我是标题'}
},
body() {
return {type:'section',children:'我是内容'}
},
foote() {
return {type:'p',children:'我是注脚'}
}
}
}
}

组件模板中的插槽内容会被编译为插槽函数,而插槽函数的返回值就是具体的插槽内容

MyComponent的模板会被编译成如下渲染函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function render() {
return [
{
type: "header",
children: [this.$slots.header()]
},
{
type: 'div',
children: [this.$slots.body()]
},
{
type: 'footer',
children: [this.$slots.footer()]
}
]
}

渲染插槽内容的过程,就是调用插槽函数并渲染由其返回的内容的过程,在运行时实现上,插槽依赖于setupContext中的slots对象,

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
function mountComponent(vnode,container,anchor) {
//...
//直接使用编译好的vnode.children对象作为slots对象
const slots = vnode.children || {}
//将slot对象添加到setupContext中
const setupContext = {attrs,emit,slots}
const instance = {
state,
props: shallowReactive(props),
isMounnted:false,
subTree:null,
//将插槽添加到组件实例
slots
}
const renderContext = new Proxy(instance,{
get(t,k,t){
const {state,props,slots} = t
//当k的值为$slots,直接返回组件实例上的slots
if(k === '$slots')return slots
//...
},
set(t,k,v,r){
//...
}
})
}