关于vue.js:vue源码分析组件化

27次阅读

共计 5367 个字符,预计需要花费 14 分钟才能阅读完成。

在上一篇文章中我介绍了 vue 从实例化到渲染到页面的具体流程,本文基于该文章介绍组件的创立到渲染的具体流程,咱们将从源码的⾓度来剖析 Vue 的组件外部是如何⼯作的,只有理解了外部的⼯作原理,能力让咱们使⽤它的时候更加得⼼应⼿。

vue 源码绝对比较复杂,须要急躁重复了解及调试,不懂就多调试问百度,罗马不是一日建成的,置信保持就会有播种哈~

咱们先写个测试页面,具体调试能够下载 vue.js,而后具体做断点 debugger 调试。

  <script src="./vue.js"></script>
  <div id="app"></div>
  <script>
    var Child = {
      name: 'childcom',
      template: '<div>{{msg}}</div>',
      data() {
        return {msg: 'child msg',}
      }
    }
    var vm = new Vue({
      el: '#app',
      render(h){return h(Child)
      },
      data: {message: 'Hello',}
    })
  </script>

先上图剖析流程,绿线为组件化流程走向。

1. 执行 render 函数

由上一篇文章可知,页面渲染次要是执行 vm._update(vm._render(), hydrating), 即先执行vm._render() 函数获取 vnode 虚构 dom, 而后再 vm._update(vnode) 把虚构 dom 更新到页面上, 对应到理论开发中,也就是下面测试页中的 render(h){return h(Child) }, 此处的 h 即为 createElement , createElement 通过参数重载之后调用 _createElement

_createElement 次要是依据 tag,标签判断是组件还是一般 node 标签,返回对应的 vnode 虚构 dom,所以外围在于 createComponent 函数

function _createElement (context, tag, data, children, normalizationType) {if (typeof tag === 'string') {
    // 一般元素 创立虚构 dom
    vnode = new VNode(config.parsePlatformTagName(tag), data, children,
      undefined, undefined, context
    );
  }else{
    // 组件 创立虚构 dom
    vnode = createComponent(tag, data, context, children);
  }
}

2. 执行 createComponent 函数

createComponent 的逻辑也会有⼀些简单,然而剖析源码⽐较举荐的是只剖析核⼼流 程,分⽀流程能够之后针对性的看,所以这⾥针对组件渲染这 3 个关键步骤:

  • 创立 Vue.extend 函数
 // 在 vue 初始化 initGlobalAPI 的时候定义了  Vue.options._base = Vue
 // baseCtor.extend(Ctor) 等价于 Vue.extend(Ctor)
 const baseCtor = context.$options._base 
 if (isObject(Ctor)) {Ctor = baseCtor.extend(Ctor) }

接着看 enxtend 函数是如何构建 VueComponent 构造函数的

 Vue.extend = function (extendOptions) {
   // this 即为 Vue
   var Super = this;
   // 创立 VueComponent 构造函数
   var Sub = function VueComponent (options) {
    // 当咱们去实例化 Sub 的时候,就会执⾏ this._init 逻辑再次⾛到了 Vue 实例的初始化逻辑
     this._init(options);
   };
   // Object.create 继承 Super,也就是继承了 Vue
   Sub.prototype = Object.create(Super.prototype);
   // 更改 constructor,指向本人,实现完满继承
   Sub.prototype.constructor = Sub;
   Sub.cid = cid++;
   Sub.options = mergeOptions(
     Super.options,
     extendOptions
   );
   Sub['super'] = Super; 
   return Sub  
 }

能够学习通过 Object.create 的形式继承类,这也是面试中常常会遇到的问题。VueComponent 继承了 Vue,而后合并 options,最初返回 Sub 构造函数。

  • 装置组件钩⼦函数
// install component management hooks onto the placeholder node
installComponentHooks(data)

整个 installComponentHooks 的过程就是把 componentVNodeHooks 的钩⼦函数合并到 data.hook 中,在 VNode 执⾏ patch 的过程中执⾏相干的钩⼦函数,具体的执⾏咱们稍后在介绍 patch 过程中会具体介绍。

  • 实例化 vnode
 // return a placeholder vnode
  var name = Ctor.options.name || tag;
  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

通过以上三个步骤可知组件的实例化是由 Vue.extend 中 VueComponent 构造函数实现的,并且 createComponent 最终返回了 vnode,以执行 vm._update(vnode) 渲染页面。

3. 执行 _update 函数

_update 执行 patch,也就是调用 path 函数

 function patch (oldVnode, vnode, hydrating, removeOnly) {
    // 通过 vnode 创立新的实在 dom 节点
    createElm(
      vnode,
      insertedVnodeQueue,
      oldElm._leaveCb ? null : parentElm,
      nodeOps.nextSibling(oldElm)
    );
    // 移除老节点
    if (isDef(parentElm)) {removeVnodes(parentElm, [oldVnode], 0, 0);
    } else if (isDef(oldVnode.tag)) {invokeDestroyHook(oldVnode);
    }
  }

path 的外围就在于 createElm,通过该函数将虚构 dom 创立为实在的 dom,接下来看 createElm

  function createElm (vnode,insertedVnodeQueue, parentElm,refElm,nested,ownerArray,index) {
    // 如果是组件则执行 createComponent
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {return}
    
    // createChildren 递归调用 createElm 生成 vnode
    createChildren(vnode, children, insertedVnodeQueue);
    
    if (isDef(data)) {
      // 实在 dom 元素  属性结构 
      invokeCreateHooks(vnode, insertedVnodeQueue);
    }
    // 父元素插入实在 dom 元素
    insert(parentElm, vnode.elm, refElm);
  }

createComponent 函数执行

let i = vnode.data
if (isDef(i = i.hook) && isDef(i = i.init)) {i(vnode, false);
}

在第二步 createComponent 的时候,通过执行 installComponentHooks(data), 将 vnode.data 挂上了 init 等钩子函数, i(vnode, false); 也就是执行 上面的 init 函数

var componentVNodeHooks = {init: function init (vnode, hydrating) {
    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 {
      // 获取 Vue.extend 构造函数实例 
      var child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      );
      // 挂载实例
      child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    }
  },
  // ....
}

此处次要通过 createComponentInstanceForVnode 实例化 VueComponent 失去实例,并挂载。

function createComponentInstanceForVnode (
  vnode, // we know it's MountedComponentVNode but flow doesn't
  parent // activeInstance in lifecycle state
) {
  var options = {
    _isComponent: true,
    _parentVnode: vnode,
    parent: parent
  };
  // check inline-template render functions
  var inlineTemplate = vnode.data.inlineTemplate;
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render;
    options.staticRenderFns = inlineTemplate.staticRenderFns;
  }
  // vnode.componentOptions.Ctor 等价于 VueComponent
  return new vnode.componentOptions.Ctor(options)
}

合并 options 并把 _isComponent 设置为 true,而后 new VueComponent(options),因为 VueComponent 是 Vue 的子类,那么实例化将从新执行一遍 Vue._init(options)。也就再执行了一遍 vm._update(vm._render(), hydrating)。最初将执行回来 createElm, 还是持续把 createElm 代码贴出来。

  function createElm (vnode,insertedVnodeQueue, parentElm,refElm,nested,ownerArray,index) {
    // 如果是组件则执行 createComponent
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {return}
    
    // createChildren 递归调用 createElm 生成 vnode
    createChildren(vnode, children, insertedVnodeQueue);
    
    if (isDef(data)) {
      // 实在 dom 元素  属性结构 
      invokeCreateHooks(vnode, insertedVnodeQueue);
    }
    // 父元素插入实在 dom 元素
    insert(parentElm, vnode.elm, refElm);
  }

如果该组件没有子组件那么 createComponent(vnode, insertedVnodeQueue, parentElm, refElm)false,那么将跳出递归一步步回到 patch 函数, 渲染页面。

其实,vue 组件化过程就是先 new Vue,而后执行 init 初始化,接着判断是否含有组件,如果没有则间接生成 vode,最初通过 path 把 vnode 渲染到页面。如果有组件,则通过 createComponent 函数,来创立 VueComponent 构造函数 (继承 Vue)。而后在 patch 的时候通过 createElm 函数中判断是否有组件,而后调用 VueComponent 函数,再执行一遍 init 初始化,直到没有组件被实例化完之后接着执行 createElm 来创立实在 dom 元素,最初把它渲染到页面上。具体能够参考下面的流程图来剖析!

正文完
 0