vm._update() _update 方法的作用是把 VNode 渲染成真实的 DOM,调用时期是首次渲染和数据改变,数据改变驱动视图变化。 vm._ patch 调用了 方法的返回值,因为把 VNode 渲染到不同平台的方法是不同的,所以使用了 createPatchFunction 进行函数柯里化,把差异函数提前固化,不同每次调用 patch 的时候传入 nodeOps 和 modules…

vm._update()

_update 方法的作用是把 VNode 渲染成真实的 DOM,调用时期是首次渲染和数据改变,数据改变驱动视图变化。

// src/core/instance/lifecycle.js
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
  const vm: Component = this;
  // 下面3个是为了数据改变时调用的update
  const prevEl = vm.$el;
  const prevVnode = vm._vnode;
  const prevActiveInstance = activeInstance;
  activeInstance = vm;
  vm._vnode = vnode;
  // 核心就是调用vm.__patch__方法
  if (!prevVnode) {
    // 定义在\src\platforms\web\runtime\index.js Vue.prototype.__patch__ = inBrowser ? patch : noop
    // 又在\src\platforms\web\runtime\index.js定义,export const patch: Function = createPatchFunction({ nodeOps, modules })
    // createPatchFunction,定义了很多函数,返回patch,vm._patch调用的是patch
    // 是为了函数柯里化,nodeOps在不同平台下是不同的,modules不同平台的是不同的
    // 如果不这样做,每次执行patch的时候,都要判断在哪个平台
    // 通过柯里化,磨平了平台差异化,每次调用patch的时候不能再次判断,通过提前判断差异化参数,在使用的时候不用再次判断
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
  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.
};
  1. vm.__patch__

    vm._patch 调用了 createPatchFunction 方法的返回值,因为把 VNode 渲染到不同平台的方法是不同的,所以使用了 createPatchFunction 进行函数柯里化,把差异函数提前固化,不同每次调用 patch 的时候传入 nodeOps 和 modules,nodeOps 是一些平台 DOM 的操作方法,modules 是平台的一些模块,在整个 patch 过程中,会执行一系列的钩子函数

// 相当于调用vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false),这个例子中,最里面是文本vnode,实际上是递归创建了一个完整的dom树,并插入到body中
var app = new Vue({
  el: '#app',
  render: function(createElement) {
    return createElement(
      'div',
      {
        attrs: {
          id: 'app'
        }
      },
      this.message
    );
  },
  data: {
    message: 'Hello Vue!'
  }
});
  1. patch
// \src\core\vdom\patch.js
// 参数1:oldVnode 老的VNode的节点,
// 参数2:vnode执行_render后的VNode的节点
// 参数3:hydrating表示是否服务端渲染
// 参数4:removeOnly
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);
    // 如果是真实的DOM元素,新老node相同,主要是在数据更新时调用
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // patch existing root node
      patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
    } else {
      // 如果是真实的DOM元素,在首次渲染时调用
      if (isRealElement) {
        // mounting to a real element
        // check if this is server-rendered content and if we can perform
        // a successful hydration.
        if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
          oldVnode.removeAttribute(SSR_ATTR);
          hydrating = true;
        }
        if (isTrue(hydrating)) {
          if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
            invokeInsertHook(vnode, insertedVnodeQueue, true);
            return oldVnode;
          } else if (process.env.NODE_ENV !== 'production') {
            warn('The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '<p>, or missing <tbody>. Bailing hydration and performing ' + 'full client-side render.');
          }
        }
        // 把 oldVnode 转换成 VNode
        oldVnode = emptyNodeAt(oldVnode);
      }

      // replacing existing element
      const oldElm = oldVnode.elm;
      const parentElm = nodeOps.parentNode(oldElm);

      // 通过虚拟节点创建真实的 DOM 并插入到它的父节点
      createElm(
        vnode,
        insertedVnodeQueue,
        // extremely rare edge case: do not insert if old element is in a
        // leaving transition. Only happens when combining transition +
        // keep-alive + HOCs. (#4590)
        oldElm._leaveCb ? null : parentElm,
        nodeOps.nextSibling(oldElm)
      );

      // update parent placeholder node element, recursively
      if (isDef(vnode.parent)) {
        let ancestor = vnode.parent;
        const patchable = isPatchable(vnode);
        while (ancestor) {
          for (let i = 0; i < cbs.destroy.length; ++i) {
            cbs.destroy[i](ancestor);
          }
          ancestor.elm = vnode.elm;
          if (patchable) {
            for (let i = 0; i < cbs.create.length; ++i) {
              cbs.create[i](emptyNode, ancestor);
            }
            // #6513
            // invoke insert hooks that may have been merged by create hooks.
            // e.g. for directives that uses the "inserted" hook.
            const insert = ancestor.data.hook.insert;
            if (insert.merged) {
              // start at index 1 to avoid re-invoking component mounted hook
              for (let i = 1; i < insert.fns.length; i++) {
                insert.fns[i]();
              }
            }
          } else {
            registerRef(ancestor);
          }
          ancestor = ancestor.parent;
        }
      }

      // 如果定义了parentElm,把之前的节点删掉
      if (isDef(parentElm)) {
        removeVnodes(parentElm, [oldVnode], 0, 0);
      } else if (isDef(oldVnode.tag)) {
        invokeDestroyHook(oldVnode);
      }
    }
  }

  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
  return vnode.elm;
};
  1. createElm

    主要作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点,如果 VNode 不包含 tag,会产生一个注释节点或者纯文本节点,可直接插入到父元素中,

// \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
  // 尝试建一个组件节点,按照例子是false
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return;
  }

  const data = vnode.data;
  const children = vnode.children;
  const tag = vnode.tag;
  // 判断VNode是否包含tag,文本是没有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__) {
      // in Weex, the default insertion order is parent-first.
      // List items can be optimized to use children-first insertion
      // with append="tree".
      const appendAsTree = isDef(data) && isTrue(data.appendAsTree);
      if (!appendAsTree) {
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue);
        }
        insert(parentElm, vnode.elm, refElm);
      }
      createChildren(vnode, children, insertedVnodeQueue);
      if (appendAsTree) {
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue);
        }
        insert(parentElm, vnode.elm, refElm);
      }
    } 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);
  }
}
  1. createChildren

    遍历子虚拟节点,递归调用 createElm,传入 vnode.elm 作为父容器的 DOM 节点占位符

// \src\core\vdom\patch.js
function createChildren(vnode, children, insertedVnodeQueue) {
  if (Array.isArray(children)) {
    if (process.env.NODE_ENV !== 'production') {
      // 对key进行校验
      checkDuplicateKeys(children);
    }
    // 遍历子虚拟节点,递归调用createElm,传入vnode.elm作为富容器的DOM节点占位符
    for (let i = 0; i < children.length; ++i) {
      createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
    }
  } else if (isPrimitive(vnode.text)) {
    nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
  }
}
  1. invokeCreateHooks

    执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue

// \src\core\vdom\patch.js
function invokeCreateHooks(vnode, insertedVnodeQueue) {
  for (let i = 0; i < cbs.create.length; ++i) {
    cbs.create[i](emptyNode, vnode);
  }
  i = vnode.data.hook; // Reuse variable
  if (isDef(i)) {
    if (isDef(i.create)) i.create(emptyNode, vnode);
    // 执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue
    if (isDef(i.insert)) insertedVnodeQueue.push(vnode);
  }
}
  1. insert

    把 DOM 插入到父节点中,先调用 createChildren,子元素会优先调用 insert,进行递归,所以整个 vnode 树节点的插入顺序是先子后父

// \src\core\vdom\patch.js
function insert(parent, elm, ref) {
  if (isDef(parent)) {
    if (isDef(ref)) {
      // 有参考节点,判断参考节点和父节点是否相等
      if (ref.parentNode === parent) {
        // 把dom插入到父节点
        nodeOps.insertBefore(parent, elm, ref);
      }
    } else {
      nodeOps.appendChild(parent, elm);
    }
  }
}

暂无评论