写文章不容易,点个赞呗兄弟
专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧
研究基于 Vue版本 【2.5.17】
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧node
【Vue原理】Component - 源码版 之 挂载组件DOM dom
由这篇文章 从模板到DOM的简要流程 函数
咱们知道,在生成 VNode 以后,下一步就是根据 VNode 生成DOM而后挂载了学习
在本文开始以前你能够先看 Component - 白话版 先总体了解下componentthis
如今开始咱们的正文spa
上一篇文章 Component - 建立组件VNode ,咱们已经说到了 【页面模板解析成 VNode 树】的步骤prototype
那今天就就到了 【页面VNode生成DOM挂载】 了3d
等等,今天说的不是 Component 挂载DOM 吗?跟页面Vnode 有什么关系??是啊,component 的挂载确定是跟着父页面的啊,你本身挂?自挂东南枝吗?code
好了,废话不说,立刻开始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 过程,是一毛同样的,就很少说了
一、父页面已经拿到了 VNode,其中会调用 createElm 根据 VNode 生成DOM,进行挂载
二、不断的递归遍历子节点,使用 createComponent 判断标签是不是组件
三、遇到组件,拿到组件外壳VNode 的data(data 保存有父组件给子组件的,事件,props,构造函数,钩子)
四、从 data 中拿到 hook,hook 中拿到 init 钩子,并执行 init 钩子
五、init 钩子中,调用 createComponentInstanceForVnode 调用组件构造函数,并返回组件
六、init 钩子中,使用上一步返回的实例,手动调用 vm.$mount 进行组件内部模板解析渲染,并挂载