Vue2.0组件渲染原理

分析Vue.componet源码

使用Vue.extend,使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。实际上就是生成一个继承Vue的子类,从而继承Vue的原型方法,在调用原型上面的_init方法,进行子类的初始化,在手动调用$mount方法进行挂载。
而Vue.component内部就是使用Vue.extend方法,我们看一下源码:

export function initGlobalApi(Vue){
  Vue.options = {} // Vue.components Vue.directive
  // 混合 将mixin上面的合并到Vue上面
  Vue.mixin = function(mixin){
    // console.log('mixin',this.options)
    // 合并对象   先考虑生命周期 不考虑其他的合并 data computed  watch
    this.options = mergeOptions(this.options,mixin)
    // console.log('this.options',this.options)
  }
  // 用户 new Vue的时候({created(){}}) 也需要合并
  Vue.options._base = Vue; // Vue的构造函数 _base 最终的Vue构造函数保留在options对象中
  Vue.options.components = {}
  initExtend(Vue)
  Vue.component = function(id,definition){
    // Vue.extend
    definition.name = definition.name || id; // 默认会以name属性为准
   // 根据当前的组件对象 生成了一个子类的构造函数
   // 用的时候的得new definition().$mount()
    definition = this.options._base.extend(definition) // 永远是父类
    Vue.options.components[id] = definition;
  }
}
export function initExtend(Vue){
    let cid = 0;
    // 核心就是创造子类继承我们的父类
    Vue.extend = function(extendOptions){
        const Super = this;
        const Sub = function VueComponent(options){
            this._init(options)
        }
        Sub.cid = cid++;
        // 子类继承父类原型上面的方法
        Sub.prototype = Object.create(Super.prototype)
        // Sub.prototype.__proto = Super.prototype
        Sub.prototype.constructor = Sub;
        // 现将全局的Vue父类的options与自身的合并在初始化的时候,将
        // Sub.options与传入的options合并
        Sub.options = mergeOptions(Super.options,extendOptions)
        // console.log('Sub',Sub)
        // 处理其他的属性 mixin component
        Sub.components = Super.components;
        return Sub;
    }
    
}

这段代码是在Vue初始化的时候,会初始化一下全局的api,主要定义一些Vue的静态方法,其中包括混合以及对组件component方法的定义。
1.首先在Vue构造函数上面定义一个options对象对象包括两个属性一个是_base:Vue本身,以及components对象:存放组件对象的。
2.调用initExtend方法,并且传递Vue参数,在initExtend方法中定一个Vue构造函数的静态方法extend这个方法就是Vue.component的核心。在这个方法中:
(1)定义Sub构造函数,并且Sub构造函数继承Super也就是Vue构造函数
Object.create方法就是个生成一个对象,这个对象的__proto__属性指向Object.create内部调用的对象。这里也就是Sub.prototype.proto = Super.prototype; Sub通过原型链继承,但是这样Sub的原型的构造函数就会等于Super,所以我们给Sub重新指定 构造函数等于Sub。即: Sub.prototype.constructor = Sub;
(2)第二部调用mergeOptions方法这个方法是合并属性方法将父类Vue构造函数上面的属性和子类进行合并。
(3)处理其他的属性 比如 components,mixin等等,也就是子类继承父类静态属性方法。
(4)Vue.extend方法中返回这个继承的子类。
3.定义Vue.component静态方法,该方法中有两个参数一个是id:vue组件的id它代表组件的name,第二个参数definition就是参数对象,包括template,data,钩子函数等等。
这个方法中会给definition赋值一个属性name表示组件的name,以及调用g刚才定义的Vue.extend,即生成一个子类重新赋值给definition参数,并将Vue.options.components中增加这个子类。

好Vue.component方法看完了,我们来看到底是怎么将子组件渲染到页面上去的。

子组件是如何显示到页面上面的

我们知道vue页面渲染要经过,生成_render函数以及,调用_render函数,生成虚拟dom,然后根据虚拟dom生成真实的dom挂载到页面上面,那么组件在生成虚拟的dom的时候和普通标签是不一样的,我们看一下源码:`

function createElement(vm,tag,data={},...children){
  // 如果是组件 我产生虚拟节点时需要把组件的构造函数传入
  // 根据tag 名字 需要判断他是不是个组件
  if(isReservedTag(tag)){
    // 原生标签
    return vnode(tag,data,data.key,children)
  }else{
    // 合并后的属性
    let Ctor = vm.$options.components[tag]; // 获取到这个
    // 创建组件的虚拟节点
    // children 是组件的插槽
    return createComponent(vm,tag,data,data.key,children,Ctor)
  }
}
function createComponent(vm,tag,data,key,children,Ctor){
  // Ctor有可能是构造函数有可能是个对象
  const baseCtor = vm.$options._base; // Vue;
  if(typeof Ctor == 'object'){
    Ctor = baseCtor.extend(Ctor)
  }
  // console.log('Ctor',Ctor)
  // 给组件增加生命周期
  data.hook = {
    init(vnode){
      let child = vnode.componentIntance = new Ctor({}); // 创建组件的实例
      child.$mount() // 组件的挂载逻辑 组建的$mount方法是不传递参数的
    } //稍后初始化组件时,会调用此init方法
  }
  let component = vnode(`vue-component-${Ctor.cid}-${tag}`,data,key,undefined,undefined,{Ctor,children})
  // console.log('component',component)
  return  component
}

在创建虚拟dom的时候,会先判断是否是普通标签如果是普通标签直接走创建虚拟dom的逻辑,如果不是那么就是组件标签,如果是组件标签那么根据tag获取组件的定义,这里要注意有可能是在Vue上面定义的全局组件也有可能是局部组件,所以在vue初始化的时候合并组件对象我们要采用就近原则。

strats.components = function(parentValue,childValue){
  const res = Object.create(parentValue);
  // res.__proto__ = parentValue 会像原   型链上面去找
  if(childValue){
    for(let key in childValue){
      res[key] = childValue[key]
    }
  }
  return res
}

这块就是合并component对象的我们将父类Vue上面定义的组件作为子类的__proto__上面的对象。
接着:开始创建组件的虚拟dom,调用createComponent方法,如果Ctor是对象的话,那么就调用Vue的extend 将它变成Vue的子类构造函数,然后给组件的属性增加hook属性,这个属性增加hook方法,这个方法是用于初始化Vue子类,并且进行挂载的,接着将生成的组件虚拟dom返回出去。

生成虚拟dom结束以后,该开始生成真实的dom了

生成真实的dom,先生成最外层的dom,然后遍历children,如果tag存在的话是标签节点的话调用creactComponent方法,`

function creactComponent(vnode){
  // debugger
  // 调用 hook中的init方法
  let i = vnode.data
  if((i = i.hook) && (i = i.init)){
    i(vnode) // 内部会去new这个组件  new晚以后 会将实例挂载在vnode上面
  } // i就是init方法
  // 是否是虚拟节点
  if(vnode.componentIntance){
    return true;
  }
}

这个方法获取虚拟节点的属性并将属性的hook.init方法取出来并且将虚拟节点传递进去,然后调用,这个方法中首先会生成一个子类的实例,刚才前面说了这个子类默认会调用_init方法,在_init方法中,会进行一系列的初始化操作,但是没有 e l 属 性 , 然 后 进 行 挂 载 el属性,然后进行挂载 elmount,。挂摘操作直接将生成的子组件的真实节点返回过来。并且j将创建的真实节点赋值给这个vnode虚拟节点componentInstance.$el。这个真实的节点返回给父节点的子节点中插入到子节点上面。
所以总体经过了父类状态初始化,父类挂载,父类的生成虚拟dom(包含父类儿子组件虚拟dom的生成),父类真实节点的创建,子类子组件真实节点创建(这包括子类的初始化,挂载),最后所有真实节点全部创建完成。

下面代码正确输出是什么

Vue.component('my-button',{
    // Vue.extend
    data(){
      return {
        a:111
      }
    },
    name:'aa',
    template:'<button>{{a}}</button>',
    beforeCreate(){
      console.log('child beforeCreate')
    },  
    created(){
      // 生命周期  就是回调函数  先订阅号 后序会触发此方法
      console.log('child created')
    },  
    beforeMount(){
      console.log('child beforeMount')
    },
    mounted(){
      console.log('child mounted')
    },
  })
  // options Api 通过一个选项进行配置
  Vue.mixin({
    created:function a(){
      // 生命周期  就是回调函数  先订阅号 后序会触发此方法
      // console.log('created...')
    }
  })
  Vue.mixin({
    created:function b(){
      // 生命周期  就是回调函数  先订阅号 后序会触发此方法
      // console.log('created...')
    }
  })
  // 组件的合并策略,就近原则
  let vm = new Vue({
    el:'#app',
    data(){
      return {
        firstName:'綦',
        lastName:'旭'
      }
    },
    components:{
      aa:{
        template:`<div>a</div>`
      },
      bb:{
        template:`<div>b</div>`
      }
    },
    
    beforeCreate(){
      console.log('parent beforeCreate')
    },  
    created(){
      // 生命周期  就是回调函数  先订阅号 后序会触发此方法
      console.log('parent created')
    },  
    beforeMount(){
      console.log('parent beforeMount')
    },
    mounted(){
      console.log('parent mounted')
    },
    updated(){
      // console.log('updated')
    }
    // template:``
  })

我们知道了组件是怎么渲染的了,那么我们也理解了声明周期是怎么个先后顺序了。
先执行父类的初始化状态之前,然后初始化之后,然后挂载之前,在挂载完成之前会先进行子类的初始化,挂载,所有的子类挂载完成以后,最后是父类挂载完成。
最后结果截图。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值