preface

Last time we looked at the process from vNode creation to generation. Now let’s explore how Vue converts vNodes into real DOM nodes/elements

Vue.prototype._update

The _render function we mentioned last time was actually passed in as an argument to the _update function, in other words, after the _render function ends, _update will execute 👇

Vue.prototype._update = function (vnode, hydrating) {
    var vm = this;
    var prevEl = vm.$el;
    var prevVnode = vm._vnode;
    var restoreActiveInstance = setActiveInstance(vm);
    vm._vnode = vnode;
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if(! prevVnode) {// initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode);
    }
    restoreActiveInstance();
    // 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.
  };
Copy the code

Here’s the logic of the code:

  • callsetActiveInstance(vm)Set the currentvmIs an active instance
  • judgepreVnodeIf yes, callvm.$el = vm.__patch__(prevVnode, vnode);, otherwise callvm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);(Essentially the difference between the first rendering and the second update)
  • callrestoreActiveInstance()Reset an active instance
  • rightHOCI made a special judgment (because I didn’t use itHOC, so skip it.)

The setActiveInstance function returns only a closure function (which is not important). If you need to know more, how is the __patch__ function implemented

Other relevant codes:

updateComponent = function () { vm._update(vm._render(), hydrating); }; . new Watcher(vm, updateComponent, noop, {before: function before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate'); }}},true /* isRenderWatcher */);
Copy the code

__patch__

It may surprise you, but the __patch__ function implementation is actually quite simple 👇

var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); . Vue.prototype.__patch__ = inBrowser ? patch : noop;Copy the code

Obviously, createPatchFunction also returns a closure function

patch

Although __patch__ looks very simple, but in fact the internal implementation of the logic is quite complex, the amount of code is also very much 👇

return function patch (oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
      return
    }

    var isInitialPatch = false;
    var insertedVnodeQueue = [];

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true;
      createElm(vnode, insertedVnodeQueue);
    } else {
      var isRealElement = isDef(oldVnode.nodeType);
      if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly);
      } else {
        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.'); }}// either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode);
        }

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

        // create new node
        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)) {
          var ancestor = vnode.parent;
          var patchable = isPatchable(vnode);
          while (ancestor) {
            for (var i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor);
            }
            ancestor.elm = vnode.elm;
            if (patchable) {
              for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
                cbs.create[i$1](emptyNode, ancestor);
              }
              / / # 6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              var insert = ancestor.data.hook.insert;
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
                  insert.fns[i$2] (); }}}else{ registerRef(ancestor); } ancestor = ancestor.parent; }}// destroy old node
        if (isDef(parentElm)) {
          removeVnodes(parentElm, [oldVnode], 0.0);
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode);
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
    return vnode.elm
  }
Copy the code

With so much code, it’s certainly too much to digest at once, so we can take a tentative look at 👇 with the following questions

  • For the first timepatchOperation and subsequentpatchWhat is the difference between operations?
  • domWhat are the rules when a change is made between nodes, or when “new nodes” replace “old nodes”?

patchThe special logic of a function

Patch function has special logic for initial rendering. Obviously, we only need to go through the logic of patch for the first time to make it clear 👇

Combined with the above source code, summed up the idea here:

  • If old node is empty, it is calledcreateElm(vnode, insertVnodeQueue)To create a new node directly.
  • If the old node existsdomNode is divided into the following steps:
    • Remove the “old node”SSR_ATTRAttribute (if present)
    • Determine if it is “rendering” (hydrating)
      • Is to performhydrate(oldvnode, vnode, insertVnodeQueue)Then check whether the command is executed successfully
        • Trigger on successinvokeInsertHook(vnode, insertVnodeQueue, true)
        • Issue “warning” after failure (test environment)
      • Otherwise the callemptyNodeAt(oldVnode), to the “old node” (actuallydomNode) generated it.”vnode

A “forgotten” line of code

After reading the source code, it is not difficult to find that the above combed logic is missing this code:

if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
    patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly);
}
Copy the code

That is to do the operation of patchVnode once for “the same node of non-DOM element”. This code can be broken down into several points:

  • What is “same node”?
  • patchVnodeWhat did you do?

“Same Node”

According to the semantics, we should look at this part of the code 👇

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
Copy the code

The logic of sameVnode is to determine whether two VNodes are the same node according to the attributes of vNodes

patchVnode

Since the premise of executing patchVnode is that the old and new nodes are “the same” node, we have reason to believe that patchVnode is used to deal with the changes of the same node.

function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
    if (oldVnode === vnode) {
      return
    }

    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // clone reused vnode
      vnode = ownerArray[index] = cloneVNode(vnode);
    }

    var elm = vnode.elm = oldVnode.elm;

    if (isTrue(oldVnode.isAsyncPlaceholder)) {
      if (isDef(vnode.asyncFactory.resolved)) {
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue);
      } else {
        vnode.isAsyncPlaceholder = true;
      }
      return
    }

    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance;
      return
    }

    var i;
    var data = vnode.data;
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      i(oldVnode, vnode);
    }

    var oldCh = oldVnode.children;
    var ch = vnode.children;
    if (isDef(data) && isPatchable(vnode)) {
      for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }
      if(isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }}if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
        if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
      } else if (isDef(ch)) {
        if(process.env.NODE_ENV ! = ='production') {
          checkDuplicateKeys(ch);
        }
        if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ' '); }
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
      } else if (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      } else if (isDef(oldVnode.text)) {
        nodeOps.setTextContent(elm, ' '); }}else if(oldVnode.text ! == vnode.text) { nodeOps.setTextContent(elm, vnode.text); }if (isDef(data)) {
      if(isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); }}}Copy the code

Let’s look at what this code does:

  1. reusevnode(If presentelemAttributes)
  2. Working with asynchronous components
  3. Working with static nodes
  4. performprepatch(If presentdataAttributes)
  5. performupdate(If presentdataAttributes)
  6. To compareoldVnode 和 vnodeThe two nodes
  7. performpostpatch(If presentdataAttributes)

Of course, the most intuitive here is to compare the logic of oldVnode and vnode 👇

The rest of the logic can be left for the next article

Scan the QR code below or search for “Teacher Tony’s front-end cram school” to follow my wechat official account, and then you can receive my latest articles in the first time.