乐趣区

Vue原理Component-源码版-之-创建组件VNode

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

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面 关注公众号 也可以吧

【Vue 原理】Component – 源码版 之 创建组件 VNode

今天就要开启我们 Component 探索之旅,旅途有点长,各位请坐好,不要睡着了

内容的主题是,Component 的创建过程,从调用 component,到 component 挂载,到底经历了什么?欢迎来到 component 的内心世界

建议可以先看看白话版

Component – 白话版

Component 创建,我主要分了两个流程

1、创建 组件 VNode
2、挂载 组件 DOM

每个流程涉及源码都很多,所以每个流程写一篇文章。没错了,今天讲的就是 创建组件 VNode


场景设置

首先,我们假定现在有这么一个模板,使用了 test 组件

然后页面噼里啪啦执行到了 准备挂载 DOM 的步骤(之前的部分跟本主题无关,跳过)

然后页面准备执行渲染函数 render,嗯,就是执行上面模板生成的渲染函数,如下

没有错,我们的 Vue 已经走到了这一步,那么我们的突破口是什么?

没错,就是 _c

function _createElement(context, tag, data, children) {    

    var vnode;        

    var options = context.$options    

    // 从父组件选项上拿到 对应的组件的选项

    var Ctor = options.components[tag]    

    if (正常的 HTML 标签) {.... 直接新建 VNode}    

    else if (Ctor) {

        vnode = createComponent(

            Ctor, data, context, 

            children, tag

        );

    }    

    return vnode

}

今天讲的是 component,跳过其他,直接走到 第二个 if,嗯,他调用了一个 createComponent

好的,我去前面探探路

function createComponent(
    Ctor, data, context, 

    children, tag

) {    

    var baseCtor = context.$options._base;  

    // 创建组件构造函数
    Ctor = baseCtor.extend(Ctor);    

    var vnode = new VNode("vue-component-" + (Ctor.cid) + name, 
        data, undefined, undefined, 

        undefined, context,

        {Ctor: Ctor}
    );    

    return vnode

}

这个 createComponent 什么鬼的,作用大概是

1、创建组件构造函数

2、处理父组件给子组件的数据

3、创建组件 外壳 VNode

由于处理数据什么的,跟本内容无关,所以其他源码一律去掉,那么就只剩下两个流程

下面就开始这两个流程


创建组件构造函数

上面的源码中有两句话(如下),作用就是为组件创建一个构造函数!

var baseCtor = context.$options._base;
Ctor = baseCtor.extend(Ctor);

看得懂吗?看懂了?好吧,那我就不讲了

算了,算了,还是讲吧,毕竟当时自己也是懵逼的

首先,context 是什么?

context 是执行整个渲染函数的上下文对象,很明显,这里就是页面的 实例 vm 了

那么,$options 是什么?

$options 就是 实例自定义选项 和 全局选项合并之后的 产物

Vue.prototype._init = function(options) {
    .....

    vm.$options = mergeOptions( // 把两个对象合并       

        vm.constructor.options, 
        options, vm
    );
    .....

}

vm.constructor 是什么?

没错,就是 Vue,你使用 new Vue 创建的页面,构造函数肯定是 Vue 啦

vm.constructor.options 是什么?

看下面

Vue.options = Object.create(null);

Vue.options.components = Object.create(null);
Vue.options.directives = Object.create(null);
Vue.options.filters = Object.create(null);

Vue.options._base = Vue;

Vue 在引入的时候,就完成了很多初始化的内容,这里就是其中给 Vue 增加 options 的部分

你看到的 component 啊,filter 什么的啊,没错,保存的就是你全局注册的 component,filter

然后每个页面都能使用到 全局组件,全局 filter 的原因

就是因为在 页面实例初始化的时候,把 页面选项 和 全局选项私下合并

然后,你应该能看到这一句,保存了 Vue 构造函数在 options._base 中

Vue.options._base = Vue;

那么,你应该能理解前面出现的源码了

var baseCtor = context.$options._base;

没错!baseCtor 拿到的就是 Vue!!!!

然后还有一句

Ctor = baseCtor.extend(Ctor);

既然 baseCtor 是 Vue,那 baseCtor.extend 是 Vue.extend?没有错!

正是他!完成了创建组件构造函数的伟大之举!!让我们一起来欣赏下

Vue.extend = function(extendOptions) {    

    // this 指向 Vue 
    var Super = this;    

    var Sub = function VueComponent(options) {this._init(options); 

    };    
    // 原型链继承

    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;    

    // Super 永远是 Vue,所以这里就是 合并全局选项

    // 现在 Super 就是 vue,把 Vue 和 Sub 合并

    // 是把一些全局的组件 指令合并到 Sub 中

    Sub.options = mergeOptions(        

        // optios 还包括 mixins 注入的全局

        Super.options, 
        extendOptions

    );   

    return Sub

};

这个函数,会返回一个函数 VueComponent,他就是组件的构造函数!用来下篇文章创建组件实例的!!

上面的源码,做的事,简单说,就是继承父类 Vue,然后合并 options 等

最后,提一下,所有实例的父类构造函数 Super 都是 Vue

并不是说,组件 a 有一个子组件 b,然后组件 b 的父类构造函数就是 a.contructor,这是不对的,永远是 Vue,谁都是 Vue


创建组件外壳 VNode

现在就是前面代码 createComponent 中的最后一步了

注意注意,这里创建的是【组件外壳节点】,内部节点还没有上场,在下篇文章才出现

至于,什么是外壳节点,去看下我的 VNode – 源码版

跳到相关内容看就好了

var vnode = new VNode(
    "vue-component-" + Ctor.cid + name, 
    data, undefined, undefined, 

    undefined, context, 

    {Ctor: Ctor}
);

那么这个外壳节点的作用是什么?

1、保存刚创建好的组件构造函数,下篇文章中会调用到

2、保存父组件给子组件 关联的数据,比如 event,props 之类的(由于跟本主题无关,为了整洁,统统去掉了)


总结

Component 创建 外壳节点的流程,总结如下

1、页面渲染函数执行

2、_c(‘test’) 执行

3、createElement 碰到 tag 是一个组件

4、从父组件中,拿到 test 组件的 options,传入 createComponent(作用是创建构造函数和 VNode)

5、createComponent 调用 Vue.extend 创建组件构造函数

6、新建 VNode,并把构造函数和父组件给子组件的数据保存进去

7、返回 VNode

退出移动版