写文章不容易,点个赞呗兄弟 专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】node
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧bash
今天咱们来解读Slot 的源码啦。咱们都知道 Slot 分为 普通Slot 和 做用域Slot,两个内容都不少,因此分两部分进行讲述。学习
今天讲的是普通Slot!ui
其实普通Slot,表示默认Slot和 具名Slot,只是他们的处理方式都差很少,就只是是否有自定义名字而已,因此,表示一种类型。this
而咱们就以默认Slot为例去探索,让咱们先设置一个模板例子spa
父组件模板prototype
test 组件被定义在父组件中3d
new Vue({
el: document.getElementsByTagName("div")[0],
components: {
test: {
template: `
<main>
我在子组件里面
<slot></slot>
</main> `
}
},
data() {
return {
name: 11
}
}
})
复制代码
分两个问题去看code
一、插槽内容怎么解析
二、插槽如何插子页面
插槽的做用域,是父实例。就是说,普通插槽的变量,都是从父实例上获取的,好比上面例子插槽内的name
根据上面的例子,父组件被解析成下面的渲染函数
with(this) {
return _c('div', {},
[ _c('test', [
"我是放在组件的 slot " + name
])
], 1)
}
复制代码
父渲染函数执行时,会绑定父实例为执行做用域,根据 with 的做用,test 的 slot 内的变量name,就会访问父实例上的name。
那么,当父渲染函数执行时,test组件的slot,全部变量访问父实例,并开始解析,解析的流程跟普通的模板节点是同样的
当父渲染函数执行完毕,会获得一个完整的VNode,上面存储着描述DOM 的全部信息,用于去建立须要的DOM。
上面的父组件,会获得这么一个vnode
{
tag:'div',
children:[
{
tag:'test',
children:['我是放在组件的 slot 11']
}
]
}
复制代码
能够看到
一、test组件, 被当作是 父组件的一个子元素
二、test 组件内的slot ,被当作是 test元素的子元素
虽然,并不会存在 test 这种标签的元素,可是Vue统一对待,后面才会特殊处理
一、test 组件内部解析
当父组件解析成功,获得一个vnode,那么下一步就是patch(建立DOM并插入页面)
此时,Vue会按照渲染好的vnode,生成对应的DOM 树,并插入到页面中
当Vue遍历到上面的vnode的children时,遇到了 test 这个节点,发现没有test这种标签,认定他是一个组件以后,会当作一个组件去解析
这个解析的流程不会戏说细说,不属于Slot 的内容,后面的文章会讲
二、Slot 转存
解析 test 组件时,使用 _init 方法初始化 test 组件的实例
Vue.prototype._init = function(options) {
var vm = this;
if (若是是组件) {
initInternalComponent(vm, options);
}
initRender(vm);
}
复制代码
初始化test实例时,上面的两个方法会起到转存 Slot 的做用
一、initInternalComponent 把 test 组件插槽节点 【 ['我是放在组件的 slot 11'] 】 传给组件选项的 【_renderChildren】 中
function initInternalComponent(vm, options) {
// 这个options是全局选项和组件设置选项的合集
var opts = vm.$options =
Object.create(vm.constructor.options);
var componentOptions = parentVnode.componentOptions;
// 传给组件选项_renderChildren
opts._renderChildren = componentOptions.children;
}
复制代码
二、initRender 把上一步保存在 组件选项的【_renderChildren】 放在实例的【$slot】中
function initRender(vm) {
var options = vm.$options;
// 保存给组件实例上
vm.$slots = resolveSlots(options._renderChildren, renderContext);
}
function resolveSlots(children, context) {
var slots = {};
for (var i = 0,l = children.length; i < l; i++) {
var child = children[i];
var data = child.data;
if (若是是具名slot) {}
else {
(slots.default || (slots.default = [])).push(child);
}
}
return slots
}
复制代码
看父组件下的 test 组件的 vnode
{
tag:'test',
children:['我是放在组件的 slot 11']
}
复制代码
通过这两步处理,插槽节点 转存到了实例上(由于没有给名字,因此默认是default,若是给了名字,就是你给的)
testVm.$slot={
default: ['我是放在组件的 slot 11']
}
复制代码
三、slot 替换到子组件
紧接着,test 实例化初始化完毕,开始使用组件模板去构建他的渲染函数
模板被解析成下面的渲染函数
with(this) {
return _c('main', [
"我在子组件里面",
_t("default")
], 2)
}
复制代码
你能够看到,子组件的模板中的占位符 slot,被解析成了 _t 函数
_t("default")
复制代码
而后,test 渲染函数执行,其中 _t('default') 先执行
_t 是 renderSlot 函数,Vue 会给每一个实例都保存一个 _t
做用是根据传入的名字,返回实例上$slot 保存的对应的 【插槽节点】
function installRenderHelpers(target) {
target._t = renderSlot;
}
function renderSlot(name) {
return this.$slots[name]
}
复制代码
_t('default') 执行完毕,返回插槽节点,因而 test 组件渲染函数就变成下面
with(this) {
return _c('main', [
"我在子组件里面",
['我是放在组件的 slot 11']
], 2)
}
复制代码
如今,Slot 就彻底插入到子组件中啦,剩下的部分,就是渲染DOM 流程,已经跟 slot 没有关系啦
用一张图来看下流程