您的位置 首页 > 腾讯云社区

Vue中的组件从初始化到挂载经历了什么---ssh1995

下面的所有解析都以这段代码为基准:

new Vue({ el: "#app", render: h => h(AppSon) }); 复制代码

其中 AppSon 就是组件,它是一个对象:

const AppSon = { name: "app-son", data() { return { msg: 123 }; }, render(h) { return h("span", [this.msg]); } }; 复制代码

这样一段代码,在 Vue 内部组件化的流程顺序:

$createElement,其实 render 接受的参数 h 就是this.$createElement的别名createElement,做一下参数的整理,就进入下一步_createElement,比较关键的一步,在这个方法里会判断组件是span这样的 html 标签,还是用户写的自定义组件。createComponent,生成组件的 vnode,安装一些 vnode 的生命周期,返回 vnode

其实,render 函数最终返回的就是vnode。

流程解析$createElement

调用createElement方法,第一个参数是 vm 实例自身,剩余的参数原封不动的透传。

vm.$createElement = function(a, b, c, d) { return createElement(vm, a, b, c, d, true); }; 复制代码createElementfunction createElement ( // 上一步传进来的vm实例,在哪个组件的render里调用,context就是哪个组件的实例。 context, // 在例子中,就是AppSon这个对象 tag, // 可以传入props等交给子组件的选项 data, // 子组件中间的内容 children, ... ) 复制代码

之后有一个判断

if (typeof tag === "string") { // html标签流程 } else { // 组件化流程 vnode = createComponent(tag, data, context, children); } 复制代码

createComponent接受的四个参数就是上文的方法传进去的

createComponentfunction createComponent( // 还是上文中的tag,本文中是AppSon对象 Ctor, // 下面的都一致 data, context, children ) { if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); } // 给vnode安装一些生命周期函数(注意这里是vnode的生命周期,而不是created那些组件声明周期) installComponentHooks(data); var vnode = new VNode( "vue-component-" + Ctor.cid + (name ? "-" + name : ""), data, undefined, undefined, undefined, context, { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory ); return vnode; } 复制代码

下面有一个逻辑

if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); } 复制代码

其中baseCtor.extend(Ctor)就可以暂时理解为 Vue.extend,这是一个全局共用方法,从名字也可以看出它主要是做一些继承,让子组件的也拥有父组件的一些能力,这个方法返回的是一个新的构造函数。

组件对象最终都会用 extend 这个 api 变成一个组件构造函数,这个构造函数继承了父构造函数 Vue 的一些属性

extend 函数具体做了什么呢?

createComponent / Vue.extendVue.extend = function(extendOptions) { extendOptions = extendOptions || {}; // this在这个例子其实就是Vue。 var Super = this; // Appson这个组件的构造函数 var Sub = function VueComponent(options) { // 这个_init就是调用的Vue.prototype._init this._init(options); }; // 把Vue.prototype生成一个 // { __proto__: Vue.prototype }这样的对象, // 直接赋值给子组件构造函数的prototype // 此时子组件构造函数的原型链上就可以拿到Vue的原型链的属性了 Sub.prototype = Object.create(Super.prototype); Sub.prototype.constructor = Sub; // 合并Vue.option上的一些全局配置 Sub.options = mergeOptions(Super.options, extendOptions); Sub["super"] = Super; // 拷贝静态函数 Sub.extend = Super.extend; Sub.mixin = Super.mixin; Sub.use = Super.use; // 返回子组件的构造函数 return Sub; }; 复制代码

到了这一步,我们一开始定义的 Appson 组件对象,已经变成了一个函数,可以通过 new AppSon()来生成一个组件实例了,并且组件配置对象被合并到了Sub.options这个构造函数的静态属性上。

createComponent / installComponentHooks

installComponentHooks这个方法是为了给 vnode 上加入一些生命周期函数,

其中有一个init生命周期,这个周期后面被调用的时候再讲解。

createComponent / new VNode

可以看出,主要是生成 vnode 的实例,并且赋值给vnode.componentInstance,并且调用$mount方法挂载 dom 节点,注意这个init生命周期此时还没有调用。

到这为止render的流程就讲完了,现在我们拥有了一个vnode节点,它有一些关键的属性

vnode.componentOptions.Ctor: 上一步extend生成的子组件构造函数。vnode.data.hook: 里面保存了init等 vnode 生命周期方法vnode.context: 调用$createElement 的是哪个实例,这个 context 就是谁。$mount

最外层的组件调用了$mount后,组件在初次渲染的时候其实是递归去调用createElm的,而createElm中会去调用组件 vnode 的init钩子。

if (isDef((i = i.hook)) && isDef((i = i.init))) { i(vnode); } 复制代码

然后就会走进 vnode 的init生命周期的逻辑

const child = (vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance )); child.$mount(vnode.elm); 复制代码

createComponentInstanceForVnode:

createComponentInstanceForVnode ( vnode: any, parent: any, ): Component { const options: InternalComponentOptions = { // 标记这是一个组件节点 _isComponent: true, // Appson组件的vnode _parentVnode: vnode, // 当前正在活跃的父组件实例,在本例中就是根Vue实例 // new Vue({ // el: "#app", // render: h => h(AppSon) // }); parent } return new vnode.componentOptions.Ctor(options) } 复制代码

可以看出,最终调用组件构造函数,然后调用_init 方法,它接受到的 options 不再是

{ data() { }, props: { }, methods() { } } 复制代码

这样的传统 Vue 对象了,而是

{ _isComponent: true, _parentVnode: vnode, parent, } 复制代码

这样的一个对象,然后_init 内部会针对这样特征的对象,调用initInternalComponent做一些特殊的处理, 这里有一个疑惑点,那刚刚子组件声明的 data 那些选项哪去了呢? 其实是被保存在Ctor.options里了。

然后在initInternalComponent中,把子组件构造函数上保存的 options 再转移到vm.$options.__proto__上。

var opts = (vm.$options = Object.create(vm.constructor.options)); 复制代码

之后生成了子组件的实例后,又会调用child.$mount(vnode.elm),继续的去递归这个初始化的过程。

---来自腾讯云社区的---ssh1995

关于作者: 瞎采新闻

这里可以显示个人介绍!这里可以显示个人介绍!

热门文章

留言与评论(共有 0 条评论)
   
验证码: