patch patch 过程是深度遍历的过程,每次把正在激活的 vm 实例赋给 activeInstance,初始化子组件时把 activeInstance 作为一个参数传入:在 initLifecycle 拿到当前激活的 vm 实例,获取当前激活的 vm 的 parent,把当前激活 vm 实例 push 到 parent.$children中,vm.$parent 指向当前 vm 的父实例,i…

patch

patch 过程是深度遍历的过程,每次把正在激活的 vm 实例赋给 activeInstance,初始化子组件时把 activeInstance 作为一个参数传入:在 initLifecycle 拿到当前激活的 vm 实例,获取当前激活的 vm 的 parent,把当前激活 vm 实例 push 到 parent.$children中,vm.$parent 指向当前 vm 的父实例,initLifecycle 结束后,返回子组件的 vue 实例,再调用 child.$mount,最终会调用mountComponent,然后执行vm_render方法, 让vm.$vnode = _parentVnode,同时让渲染 vnode 的 parent,指向_parentVnode,return 渲染 vnode,然后执行_update,把渲染 vnode 赋给_vnode,_vnode 与$vnode是父子关系,_vnode是负责渲染的,$vnode 是一个占位符 vnode,当子组件执行 update 的时候,让当前 activeInstance 是 vm,这样子组件再创建一个子组件实例时,activeInstance 就可以作为一个父 vm 实例,然后再通过 initLifecycle 去建立父子组件关系,这样就实现了深度遍历,不断把当前 vue 实例赋给 activeInstance,同时通过 prevActiveInstance 去记录上一个,patch 完了,就可以恢复到上一个 activeInstance,这样就保证了 prevActiveInstance 和 activeInstance 是一个父子关系,在 update 的时候,会进行patch,传入当前 vnode,进行 createElm,让 parentElm 为空,子组件渲染,判断组件根 vnode 是不是一个组件,如果是组件,继续走 patch 深度遍历的逻辑,不是组件,对 children 的 data 进行判断,和普通节点渲染一样,继续遍历 children,执行 createEle 方法,如果 children 有子组件,依然会执行 createComponent,普通节点执行插入,因为此时的 parentElm 为空,parentElm 为空在执行 insert 的时候,不做任何 dom 插入操作,在调用 createComponent 时候,执行 init 方法,执行一系列的流程 patch,执行结束,执行 initComponent,把当前 vnode 的 el 返回出来,这时候 partnetElm 是有了,插入顺序是先子后父,子组件先 patch 后 insert,子组件再判断有没有组件

流程分析

  1. createElm

    创建组件 VNode,如果创建了,直接 return

  2. createComponent

    创建组件 VNode 如果 vnode 是组件 vnode,那么 i 就是 init 钩子函数,执行 init

  3. init

    通过 createComponentInstanceForVnode 创建一个 Vue 实例,从第五步回来调用$mount 方法来挂载子组件,会调用 mountComponent 方法,最后执行 vm_render 方法

  4. createComponentInstanceForVnode

    继承于 Vue 的一个构造器 Sub,相当于 new Sub(options),最终执行_init

  5. _init

    initInternalComponent 进行实例,组件自己接管了$mount 的过程,回到第三步

  6. initInternalComponent

    把之前传入的几个参数合并到内部的选项$options

  7. vm._render

    当前组件 vnode 的 parent 指向_parentVnode,也就是 vm.$vnode,生成 vnode,执行 vm._update 去渲染 VNode

  8. vm._update

    让 vm._vnode 和 vm.$vnode 变成一种父子关系 更新当前上下文的 Vue 实例

  9. vm._patch

    调用patch渲染 vnode,最后回到 createElm

  10. createElm

    如果组件的根节点是一个普通元素,那么 vm._vnode 也是普通元素,先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm,在遍历过程中,如果遇见子 vnode 是另一个组件的 vnode,从第一步开始,递归构建整个组件树

  11. insert

    完成 dom 插入,顺序是先子后父

源码分析

patch 的过程会调用 createElm 创建元素及节点

  • 1.createElm
// src/core/vdom/patch.js
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
  if (isDef(vnode.elm) && isDef(ownerArray)) {
    // This vnode was used in a previous render!
    // now it's used as a new node, overwriting its elm would cause
    // potential patch errors down the road when it's used as an insertion
    // reference node. Instead, we clone the node on-demand before creating
    // associated DOM element for it.
    vnode = ownerArray[index] = cloneVNode(vnode);
  }

  vnode.isRootInsert = !nested; // for transition enter check
  // 为true时直接结束
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return;
  }
  // 在9.patch完后
  // 这里面的vnode是组件渲染的vnode,是vm._vnode
  // 如果组件的根节点是一个普通元素,那么vm._vnode也是普通元素
  // 那么createComponent的返回值就是false,继续走下面了
  // 根组件生成vnode一致,先弄一个占位符,遍历所有子vnode,递归调用creatElm,在遍历过程中
  // 如果遇见vnode是一个组件的vnode,重复进行patch,递归的方式完整的构建了整个组件树
  // 在完成整个组件的patch过程后,最后执行insert,如果patch过程中又创建了组件,
  // 那么DOM的顺序是先父后子
  const data = vnode.data;
  const children = vnode.children;
  const tag = vnode.tag;
  if (isDef(tag)) {
    if (process.env.NODE_ENV !== 'production') {
      if (data && data.pre) {
        creatingElmInVPre++;
      }
      if (isUnknownElement(vnode, creatingElmInVPre)) {
        warn('Unknown custom element: <' + tag + '> - did you ' + 'register the component correctly? For recursive components, ' + 'make sure to provide the "name" option.', vnode.context);
      }
    }

    vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode);
    setScope(vnode);

    /* istanbul ignore if */
    if (__WEEX__) {
      // ...
    } else {
      createChildren(vnode, children, insertedVnodeQueue);
      if (isDef(data)) {
        invokeCreateHooks(vnode, insertedVnodeQueue);
      }
      insert(parentElm, vnode.elm, refElm);
    }

    if (process.env.NODE_ENV !== 'production' && data && data.pre) {
      creatingElmInVPre--;
    }
  } 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);
  }
}
  • 2.createComponent
// src/core/vdom/patch.js
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data;
  // 首先对vnode.data进行判断
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
    // 如果vnode是组件vnode,那么i就是init钩子函数
    if (isDef((i = i.hook)) && isDef((i = i.init))) {
      i(vnode, false /* hydrating */);
    }
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue);
      insert(parentElm, vnode.elm, refElm);
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
      }
      return true;
    }
  }
}
  • 3.init
// src/core/vdom/create-component.js
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      // 通过createComponentInstanceForVnode创建一个Vue实例,然后调用$mount方法来挂载子组件
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // hydrating为true为服务端渲染,
      //  相当于child.$mount(undefined, false)
      // 最终会调用mountComponent 方法,最后执行vm_render方法`7.vm._render`
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
}
  • 4.createComponentInstanceForVnode
  1. 当一个 vm 实例完成 patch 或者 update,activeInstance 会回到它的父实例
  2. 保证了 createComponentInstanceForVnode 深度遍历过程中,实例化子组件的时候能传入当前子组件的父 Vue,
  3. 并在_init 过程中,通过 vm.$parent 把这个父子关系保留
// src/core/vdom/create-component.js
export function createComponentInstanceForVnode(
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any // activeInstance in lifecycle state
): Component {
  // 先构建一个内部组件的参数
  const options: InternalComponentOptions = {
    // 代表是一个组件
    _isComponent: true,
    // 父vnode,占位符vnode,占位节点,当前激活的组件实例
    _parentVnode: vnode,
    // vm的一个实例
    parent
  };
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate;
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render;
    options.staticRenderFns = inlineTemplate.staticRenderFns;
  }
  // 继承于 Vue 的一个构造器 Sub,相当于 new Sub(options)
  // 子组件的实例化是在这个时候执行的
  // 最终执行的是_init方法,Ctor是组件的一个构造器,实际上执行的是Sub构造函数,执行子组件的构造函数,执行组件的_init
  return new vnode.componentOptions.Ctor(options);
}
  • 5._init
// src/core/instance/init.js
Vue.prototype._init = function(options?: Object) {
  const vm: Component = this;
  // a uid
  vm._uid = uid++;

  let startTag, endTag;
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`;
    endTag = `vue-perf-end:${vm._uid}`;
    mark(startTag);
  }

  // a flag to avoid this being observed
  vm._isVue = true;
  // 合并option时_isComponent为true,所以走到initInternalComponent进行实例
  if (options && options._isComponent) {
    initInternalComponent(vm, options);
  } else {
    vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
  }
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    initProxy(vm);
  } else {
    vm._renderProxy = vm;
  }
  // expose real self
  vm._self = vm;
  initLifecycle(vm);
  initEvents(vm);
  initRender(vm);
  callHook(vm, 'beforeCreate');
  initInjections(vm); // resolve injections before data/props
  initState(vm);
  initProvide(vm); // resolve provide after data/props
  callHook(vm, 'created');

  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false);
    mark(endTag);
    measure(`vue ${vm._name} init`, startTag, endTag);
  }
  // 由于组件初始化的时候不传el,组件自己接管了$mount的过程,回到了`3.init`
  if (vm.$options.el) {
    vm.$mount(vm.$options.el);
  }
};
  • 6.initInternalComponent
// src/core/instance/init.js
export function initInternalComponent(vm: Component, options: InternalComponentOptions) {
  const opts = (vm.$options = Object.create(vm.constructor.options));
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode;
  // 这两个赋值是把之前传入的几个参数合并到内部的选项$options
  // 子组件的父vm实例
  opts.parent = options.parent;
  // parentVnode是占位符
  opts._parentVnode = parentVnode;

  const vnodeComponentOptions = parentVnode.componentOptions;
  opts.propsData = vnodeComponentOptions.propsData;
  opts._parentListeners = vnodeComponentOptions.listeners;
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;

  if (options.render) {
    opts.render = options.render;
    opts.staticRenderFns = options.staticRenderFns;
  }
}
  • 7.vm._render
// src/core/instance/render.js
Vue.prototype._render = function(): VNode {
  const vm: Component = this;
  // _parentVnode就是当前组件的父vnode,而render函数生成的vnode是当前组件的渲染vnode
  // vnode的parent指向_parentVnode,也就是vm.$vnode,他们是父子关系
  const { render, _parentVnode } = vm.$options;

  // reset _rendered flag on slots for duplicate slot check
  if (process.env.NODE_ENV !== 'production') {
    for (const key in vm.$slots) {
      // $flow-disable-line
      vm.$slots[key]._rendered = false;
    }
  }

  if (_parentVnode) {
    vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;
  }

  // set parent vnode. this allows render functions to have access
  // to the data on the placeholder node.
  vm.$vnode = _parentVnode;
  // render self
  let vnode;
  try {
    vnode = render.call(vm._renderProxy, vm.$createElement);
  } catch (e) {
    handleError(e, vm, `render`);
    // return error render result,
    // or previous vnode to prevent render error causing blank component
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      if (vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
        } catch (e) {
          handleError(e, vm, `renderError`);
          vnode = vm._vnode;
        }
      } else {
        vnode = vm._vnode;
      }
    } else {
      vnode = vm._vnode;
    }
  }
  // return empty vnode in case the render function errored out
  if (!(vnode instanceof VNode)) {
    if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
      warn('Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm);
    }
    vnode = createEmptyVNode();
  }
  // set parent
  vnode.parent = _parentVnode;
  // 执行完vm_render生成vnode后,执行vm._update去渲染vnode `8.vm._update`
  return vnode;
};
  • 8.vm._update
// src/core/instance/lifecycle.js
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
  const vm: Component = this;
  const prevEl = vm.$el;
  const prevVnode = vm._vnode;
  const prevActiveInstance = activeInstance;
  // patch过程是深度遍历的过程,每次把正在激活的vm实例赋给activeInstance,初始化子组件时把activeInstance作为一个参数传入
  // 在 vm._update 的过程中
  // 把当前的 vm 赋值给 activeInstance
  // prevActiveInstance 和当前的 vm 是一个父子关系
  activeInstance = vm;
  // vnode是vm._render渲染的vnode,
  // vm._vnode 和 vm.$vnode 是一种父子关系
  // vm._vnode.parent === vm.$vnode
  vm._vnode = vnode;
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) {
    // 最后调用__patch__渲染vnode
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
  // activeInstance 是Vue实例
  // 在 lifecycle 模块的全局变量
  //  在文件顶部定义:export let activeInstance: any = null
  // Vue 整个初始化是一个深度遍历的过程
  // 在遍历的时候需要知道当前上下文是什么
  // activeInstance作为遍历时父组件的实例
  // 在实例的时候需要initInternalComponent合并option,把parent保存在vm.$options中,
  // 在 $mount 之前会调用 initLifecycle(vm)方法(5._init做了)
  activeInstance = prevActiveInstance;
  // update __vue__ reference
  if (prevEl) {
    prevEl.__vue__ = null;
  }
  if (vm.$el) {
    vm.$el.__vue__ = vm;
  }
  // if parent is an HOC, update its $el as well
  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
    vm.$parent.$el = vm.$el;
  }
  // updated hook is called by the scheduler to ensure that children are
  // updated in a parent's updated hook.
};
export function initLifecycle(vm: Component) {
  const options = vm.$options;

  // locate first non-abstract parent
  let parent = options.parent;
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent;
    }
    // 当前的 vm 存储到父实例的 $children 中
    // 8.vm._update
    // 在 vm._update 的过程中
    // 把当前的 vm 赋值给 activeInstance
    // prevActiveInstance 和当前的 vm 是一个父子关系
    // 当一个vm实例完成patch或者update,activeInstance 会回到它的父实例
    // 保证了深度遍历过程中,实例化子组件的时候能传入当前子组件的父Vue,
    // 并在_init过程中,通过 vm.$parent 把这个父子关系保留
    parent.$children.push(vm);
  }
  //  vm.$parent 就是用来保留当前 vm 的父实例
  vm.$parent = parent;
  vm.$root = parent ? parent.$root : vm;

  vm.$children = [];
  vm.$refs = {};

  vm._watcher = null;
  vm._inactive = null;
  vm._directInactive = false;
  vm._isMounted = false;
  vm._isDestroyed = false;
  vm._isBeingDestroyed = false;
}
  • 9.patch
  1. 当一个 vm 实例完成 patch 或者 update,activeInstance 会回到它的父实例
  2. 保证了 createComponentInstanceForVnode 深度遍历过程中,实例化子组件的时候能传入当前子组件的父 Vue,
  3. 并在_init 过程中,通过 vm.$parent 把这个父子关系保留
  4. 然后回到了 patch 的过程
  5. patch 又通过 createElm 渲染成 dom
return function patch(oldVnode, vnode, hydrating, removeOnly) {
  if (isUndef(vnode)) {
    if (isDef(oldVnode)) invokeDestroyHook(oldVnode);
    return;
  }

  let isInitialPatch = false;
  const insertedVnodeQueue = [];

  if (isUndef(oldVnode)) {
    // empty mount (likely as component), create new root element
    isInitialPatch = true;
    //
    createElm(vnode, insertedVnodeQueue);
  } else {
    const isRealElement = isDef(oldVnode.nodeType);
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // patch existing root node
      patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
    } else {
      // ...
    }
  }
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
  return vnode.elm;
};

总结

  1. patch 整体流程

    createComponent 返回 true->子组件初始化:init,vnode,lifecycle->子组件 render,生成子组件的 vnode->子组件 patch,遍历子组件渲染 vnode,发现还有子组件,递归调用 createComponent

  2. activeInstance

    为当前激活的 vm 实例,是深度遍历,vm 实例作为 parent 实例传入,是为了建立父子关系; vm.$vnode 为组件的占位 vnode,vm._vnode 为组件渲染的 vnode

  3. 插入顺序

    先子后父,子组件往父组件插,父组件为父组件的父组件里面插入,最终插入到 body 中

暂无评论