写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
【Vue原理】Component - 源码版 之 挂载组件DOM
由这篇文章 从模板到DOM的简要流程
我们知道,在生成 VNode 之后,下一步就是根据 VNode 生成DOM然后挂载了
在本文开始之前你可以先看 Component - 白话版 先整体了解下component
现在开始我们的正文
上一篇文章 Component - 创建组件VNode ,我们已经说到了 【页面模板解析成 VNode 树】的步骤
那今天就就到了 【页面VNode生成DOM挂载】 了
等等,今天说的不是 Component 挂载DOM 吗?跟页面Vnode 有什么关系??是啊,component 的挂载肯定是跟着父页面的啊,你自己挂?自挂东南枝吗?
好了,废话不说,马上开始
前言预告
这篇 从模板到DOM的简要流程 已经说过下面的步骤
1vm._render 执行得到 页面VNode
2vm._update 拿到 页面VNode ,会开始 patch,不断比对 【旧VNode 和 刚拿到的新VNode】
对比完之后,会调用一个 createElm 的方法去创建DOM,然后插入页面
那现在,我们就从 createElm 这个方法突破,前面的流程跟本内容无关,一律略过
function createElm(vnode, parentElm, refElm) {
// 组件需要特殊处理
if (createComponent(vnode, parentElm, refElm)) return
...正常的标签,需要不断递归子节点调用 createElm ,
然后生成DOM,并插入到父节点
}
createElm 的作用就是根据 标签名创建 DOM 节点,然后挂载到父节点中,其中参数如下
parentElm == 父DOM 节点
refElm == 兄弟DOM节点,你插入父节点,可能也要知道插在谁附近不是吗,不能乱插的
然后很明显,createElm 每次掉要给你都会调用 【createComponent】 去检测这个标签是否是组件
如果是组件,就会去创建这个组件的实例,并且 返回 true,从而不用去执行 createElm 下面的部分
调用组件生命钩子
看下 createComponent
function createComponent(vnode, parentElm, refElm) {
var data = vnode.data;
var hook = i.hook;
var init = i.init;
// 调用子组件的 init 方法, init 方法就是 Vue.prototype._init
if (init) {
// 创建子组件的 vm 实例
init(vnode, parentElm, refElm);
// 如果存在组件实例,就是上一步创建成功了
if (vnode.componentInstance) {
return true
}
}
}
有没有好奇 vnode.data.hook.init 是什么吗?
他是每个组件,都会被 【注册进外壳节点的钩子函数】,没错,就是下面的钩子,源码
什么是组件生命钩子
没错,这就是那个钩子的源码
var componentVNodeHooks = {
init(vnode, parentElm, refElm) {
var vm=
vnode.componentInstance =
createComponentInstanceForVnode(
vnode,activeInstance,
parentElm, refElm
);
// 因为 在 Vue.prototype._init 中 ,只有 $options存在 el,才会挂载 dom
// 这里手动挂载组件
vm.$mount(vnode.elm);
}
...
}
那么,钩子是什么时候注册的呢?
嗯,在上一篇文章,【创建组件外壳VNode的过程中】,然后保存到了外壳节点的 data 上
function createComponent(
Ctor, data, context,
children, tag
) {
...创建组件构造函数
var hooks = data.hook || (data.hook = {});
data.hook.init = componentVNodeHooks.init
...创建组件VNode,并保存组件构造函数 和钩子 等到 vnode 中
}
打印一下实际VNode,没错,有很多钩子,但是现在只说 init
来吧,仔细看那个init 钩子源码,你可以看到调用了一个方法
createComponentInstanceForVnode
开始深入探索它.........
创建组件实例
createComponentInstanceForVnode 函数作用就是给 component 【增加定制options】 + 【调用组件构造函数】
function createComponentInstanceForVnode(
vnode, parent,
parentElm, refElm
) {
// 增加 component 特有options
var options = {
_isComponent: true,
parent: parent, // 父实例
_parentVnode: vnode, // 外壳节点
_parentElm: parentElm , // 父DOM
_refElm: refElm // 兄弟DOM
};
// vnode.components.Ctor 就是 构造函数 ,里面会调用 Vue.prototype._init
return new vnode.componentOptions.Ctor(options)
}
vnode.componentOptions.Ctor 就是 构造函数,就是下面这个,上篇文章 Component - 创建组件VNode 时保存在外壳节点的
function VueComponent(options) {
this._init(options);
}
new 了之后,自然而然,走到了 _init 方法,在 init 方法中,有一个特殊照顾 component 的方法,专门给 component 实例设置options
"这一步跟 挂载组件DOM 没什么关联,想去掉的,但是想想还是先保留下来,完整整个流程"
Vue.prototype._init = function(options) {
if (如果是组件) {
initInternalComponent(vm, options);
}
}
组件初始化 initInteralComponent
function initInternalComponent(vm, options) {
// 这个options 就是在创建构造函数时,合并的 options,全局选项和组件设置选项
var opts = vm.$options = Object.create(vm.constructor.options);
// 保存父节点,外壳节点,兄弟节点等
var parentVnode = options._parentVnode; // _parentVnode 是外壳节点
opts.parent = options.parent; // options.parent 是 父实例
opts._parentVnode = parentVnode;
opts._parentElm = options._parentElm;
opts._refElm = options._refElm;
// 保存父组件给子组件关联的数据
var vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
// 保存渲染函数
if (options.render) {
opts.render = options.render;
opts.staticRenderFns = options.staticRenderFns;
}
}
这个时候, init 的过程就完成了
下一步就是到了 mount 过程
组件解析模板并挂载
可以再回看下 「componentVNodeHooks.init 」 那个钩子源码
在创建组件实例成功之后,会手动调用实例 vm.$mount 进行挂载,就是这句代码完成的功能
然而,挂载的步骤,就是正常标签挂载的步骤了
详情可以查看 从模板到DOM的简要流程
的 mount 过程,是一毛一样的,就不多说了
总结
1、父页面已经拿到了 VNode,其中会调用 createElm 根据 VNode 生成DOM,进行挂载
2、不断的递归遍历子节点,使用 createComponent 判断标签是否是组件
3、遇到组件,拿到组件外壳VNode 的data(data 保存有父组件给子组件的,事件,props,构造函数,钩子)
4、从 data 中拿到 hook,hook 中拿到 init 钩子,并执行 init 钩子
5、init 钩子中,调用 createComponentInstanceForVnode 调用组件构造函数,并返回组件
6、init 钩子中,使用上一步返回的实例,手动调用 vm.$mount 进行组件内部模板解析渲染,并挂载
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。