Vue的createComponent创建$vnode和_vnode的区别

4,926 阅读2分钟
main.js

new Vue({  
     el:"#app",  
    name:"container",  
    render:h=>h(App3)
})

App3是文件的入口 

<template>
     <div>
         <div>{{name}}</div> 
        <App2></App2> 
    </div> 
</template>

 子组件App2中有孙组件App1

 <template>    
    <div>        
        <div>{{name}}</div>        
        <App1></App1>    
    </div>
</template>

App1.vue

<template> 
    <div> 
         <div>{{name}}</div>
    </div>
</template>

主要流程如下(过程省略Vue的初始化)

main.js中的render会直接调用的就是_createElement,因为APP是个对象,所以会走以下逻辑

 function _createElement(context,tag,data,children,normalizationType){     else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) 
    {  
        vnode = createComponent(Ctor, data, context, children, tag);} //创建组件

    }

function createComponent (Ctor,  data,  context,  children,  tag){
    //省略无关代码,主要执行了以下两个方法
    installComponentHooks(data);// return a placeholder vnodevar //注册四个钩子函数
    name = Ctor.options.name || tag;var 
    vnode = new VNode(   //创建componentVnode,该vnode会接下来执行_update函数
     ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),  
     data, undefined, undefined, undefined, context,   
    { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },  
    asyncFactory);
}

上面返回的组件函数会作为Vue.prototype._update函数的参数,在_update函数中主要执行了patch方法,patch函数在初次渲染时会调用createElm函数

function createElm(vnode, insertedVnodeQueue, parentElm, refElm){
    //r如果vnode不是组件vnode的话,这个函数返回false,函数继续执行,创建真实dom
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return }
    //如果不是组件函数,创建真实dom
 var data = vnode.data;
    var children = vnode.children;
    var tag = vnode.tag;
    if (isDef(tag)) {
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode);
      setScope(vnode);
      /* istanbul ignore if */
      {
        createChildren(vnode, children, insertedVnodeQueue);
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue);
        }
        insert(parentElm, vnode.elm, refElm);
      }
    } else if (isTrue(vnode.isComment)) {
      vnode.elm = nodeOps.createComment(vnode.text);
      insert(parentElm, vnode.elm, refElm);
    } else {
      vnode.elm = nodeOps.createTextNode(vnode.text);
      insert(parentElm, vnode.elm, refElm);
    } 
}

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {  
//如果不是组件vnode的话,里面的i.hook为undefined,因为这些钩子函数是在_createElement里的createComponent函数注册的
    if (isDef(i)) {    
        var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;    
        if (isDef(i = i.hook) && isDef(i = i.init)) {      
        i(vnode, false /* hydrating */);    //这里箱相当于执行了this.init(vnode)
}


var componentVNodeHooks = {

  init: function init (vnode, hydrating) {
    // 实现判断是否keepAlive
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      var mountedNode = vnode; // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode);
    } else {
      // 首次渲染执行createComponentInstanceForVnode
      var child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      ); 
      child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    }
  },
  destroy:...
  prepatch:..
  insert:...}

function createComponentInstanceForVnode (vnode,parent) {
  var options = {
    _isComponent: true,
    _parentVnode: vnode,
    parent: parent
  };
  //new Vnode的时候定义了this.componentOptions = componentOptions,就是一个VueComponent构造函数
  return new vnode.componentOptions.Ctor(options) //这里会new一个新的vm,uid++,options里的_isComponent为true
}

Vue.prototype._init = function (options) {
    var vm = this;
    vm._uid = uid$3++;
    // a flag to avoid this being observed
    vm._isVue = true;
    // merge options
    if (options && options._isComponent) { //走组件初始化init的时候,_isComponent为true
      initInternalComponent(vm, options); // 新实例的options设置
    }
...}

// 新实例构建完成后,继续执行
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
子实例mount的时候也会走vm._update(vm._render())
在render函数里
 Vue.prototype._render = function () {
 vm.$vnode = _parentVnode;  //这个就是父级的createcomponent的产物,tag为组件名称
    // render self
    var vnode;
    try {
      currentRenderingInstance = vm;
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } 
    .....
    return vnode //返回的vnode作为参数传入update函数
}

Vue.prototype._update = function (vnode, hydrating) {
    var vm = this;
    var prevEl = vm.$el;
    var prevVnode = vm._vnode;
    var restoreActiveInstance = setActiveInstance(vm);
    vm._vnode = vnode; //这里给vm的_vnode赋值
    //也就是说 _vnode =  render.call(vm._renderProxy, vm.$createElement);
    .....
}

  这里的render函数是由模板字符串编译成ASTs,再有AST转换为render函数字符串

App3.vue的template


会转换成


在vue/src/core/instance/render-helpers/index.js定义了这些和渲染vnode相关的函数

_c: createElenent //这个比较特殊,是在initRender函数里面定义的


其余的 

target._v = createTextVNode 

target._s = toString

这时候的_vnode如下图所示,tag必须是html元素,完全放映了模板的结构


在执行_c("App2"),_c("App1")的时候,其实是执行了从文章主要流程开始的所有的逻辑,并将vnode返回作为父级_c方法的参数children,以此建立父子孙的vnode关系,一颗vnode tree就生成了(顺带说一句,除了最外层new Vue(...options)的根实例,每个组件都有自己对应的实例)



总结组件渲染的流程如下(只总结组件创建相关的流程)