Slot
介绍
插槽是组件中用来占位的标签,允许传递外部自定义的结构,从而更好的复用组件。 可以这么理解,与 Props 类似,props 传递的是数据,插槽传递的是的结构。
分类
- 默认插槽
- 具名插槽
- 作用域插槽
代码展示
html
<body>
<section id="app">
<layout>
<!-- 具名插槽 -->
<template #header>
<p>in header</p>
</template>
<!-- 默认插槽 -->
<div>{{ msg }}</div>
<!-- 作用域插槽 -->
<template #footer="slotProps">
<p>in footer {{ slotProps }}</p>
</template>
</layout>
</section>
<script src="../dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
msg: 'default slot'
};
},
components: {
layout: {
template: `<div class="container">
<header>
<!-- 具名插槽 -->
<slot name="header"></slot>
</header>
<main>
<!-- 默认插槽 -->
<slot></slot>
</main>
<footer>
<!-- 作用域插槽 -->
<slot name="footer" :data1="innerMsg"></slot>
</footer>
</div>`,
data() {
return {
innerMsg: 'innerMsg'
};
}
}
}
});
</script>
</body>
原理
工具函数介绍
_c: createElement
_u: resolveScopedSlots
_v: createTextVNode
_s: toString
_t: renderSlot
父组件的 render 函数
javascript
_c(
'section',
{ attrs: { id: 'app' } },
[
_c(
'layout',
{
scopedSlots: _u([
{
key: 'header',
fn: function () {
return [_c('p', [_v('in header')])];
},
proxy: true
},
{
key: 'footer',
fn: function (slotProps) {
return [_c('p', [_v('in footer ' + _s(slotProps))])];
}
}
])
},
[_v(' '), _c('div', [_v(_s(msg))])]
)
],
1
);
子组件(layout)的 render
js
_c('div', { staticClass: 'container' }, [
_c('header', [_t('header')], 2),
_v(' '),
_c('main', [_t('default')], 2),
_v(' '),
_c('footer', [_t('footer', null, { data1: innerMsg })], 2)
]);
- 执行
vm._init
- 执行
vm._render
- 执行
_u: resolveScopedSlots
,解析出作用域插槽 - 执行
_c('div', [_v(_s(msg))])
,创建出默认插槽的 vnode - 执行
_c('layout', ...
,创建组件 vnode- 此时
vnode.componentOptions.children
存储了默认插槽 vnode.data.scopedSlots
存储了作用域插槽 header、footer
- 此时
- 执行
- 得到父组件 vnode
- 调用
vm._update
即调用patch
- 第一次调用 patch,会执行创建,创建 section => 创建 layout(layout 是组件所以调用
createComponent
) - 创建组件,调用组件(layout)的
vm._init
- 调用
initInternalComponent
,将vnode.componentOptions.children
放到vm.$options._renderChildren
- 调用
initRender
, 将默认插槽放置到vm.$slots
- 调用
- 组件挂载,执行
vm._render
- 执行
normalizeScopedSlots
,将所有插槽转换成作用域插槽挂载到vm.$scopedSlots
- 执行
- 调用 render 函数
- 调用
_t: renderSlot
, 在 $scopedSlots 中通过 name 匹配出对应的函数并执行,得到插槽的 vnode 插入到当前子组件
- 调用
原理总结
在模版编译时,如果发现组件中存在子组件,那么就会将它们当作插槽,根据名称将其分为两类、默认插槽和作用域插槽,默认插槽放置在 children 中, 作用域插槽放在 scopedSlots 中。render 执行时,默认插槽会被编译成 vnode,scopedSlots 编译成函数。子组件 init 时,将父组件传递的 children 转换成 $slot, 并与父组件上的 scopedSlots 合并,生成 $scopedSlots。子组件 render 函数执行时,根据插槽的 name 在 $scopedSlots 取出对应的值,如果是 vnode 就直接使用,如果是函数就将数据传递并调用返回 vnode.