【Vue原理】Component - 源码版 之 挂载组件DOM

写文章不容易,点个赞呗兄弟 专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】node

若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧bash

【Vue原理】Component - 源码版 之 挂载组件DOM dom

由这篇文章 从模板到DOM的简要流程 函数

咱们知道,在生成 VNode 以后,下一步就是根据 VNode 生成DOM而后挂载了学习

在本文开始以前你能够先看 Component - 白话版 先总体了解下componentui

如今开始咱们的正文this

上一篇文章 Component - 建立组件VNode ,咱们已经说到了 【页面模板解析成 VNode 树】的步骤spa

那今天就就到了 【页面VNode生成DOM挂载】 了prototype

等等,今天说的不是 Component 挂载DOM 吗?跟页面Vnode 有什么关系??是啊,component 的挂载确定是跟着父页面的啊,你本身挂?自挂东南枝吗?3d

好了,废话不说,立刻开始


前言预告

这篇 从模板到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 进行组件内部模板解析渲染,并挂载

公众号
相关文章
相关标签/搜索