This is the second day of my participation in Gwen Challenge

This paper mainly introduces the whole process of Vue patch, including diff algorithm and the process of creating new nodes

preface

Vue’s virtual DOM algorithm is based on Snabbdom library, which has good speed and module mechanism. Vue Diff uses double Pointers to update the DOM while comparing.

React mainly uses the DIff queue to save the DOM to be updated, obtain the patch tree, and then update the DOM in a unified batch.

Characteristics of diff

  1. Comparisons will only be made at the same level, not across.
  2. In diff, the cycle moves from side to center

Vue patch process

_update

Vue in mount instance, mountComponent method is a key function: _update, this function is a private Vue instance methods, its role is to paint Vnode in real DOM, defined in SRC/core/instance/lifecycle. Js, Internally, it mainly calls the vm. __Patch__ method

__patch__

Vm. __patch__ is directly pointing to the Vue. Prototype. __patch__, the browser environment, the __path__ method is a method that directly pointing to patch it defined in SRC/platforms/web/runtime/index in js

patch

This patch method is defined in SRC/platforms/web/runtime/patch. The js, it is very concise, is to call the createPatchFunction function return values

createPatchFunction

The createPatchFunction method is defined in SRC /core/vnode/patch.js, and patch.js as a whole exposes only one method createPatchFunction, which returns a patch function. This is where we come to the Vue Patch node.

Let’s take a closer look at what this createPatchFunction does:

The patch function

When Vue is in patch node, it will first determine whether the new and old nodes are of the same type. If sameVNode is false, it will directly destroy oldVnode and render newVnode. Otherwise, the patchVnode method is entered

patchVnode

Next look at the patchVnode method:

PatchVnode receives six parameters: oldVnode, vnode, insertdVnodeQueue, ownerArray, index, removeOnly

PatchVnode will first determine whether it is a text node. If it is a text node, it will be good to replace the text content directly. If it is not, it will start to compare children.

If only the new nodes have children, run addVnodes to add new child nodes

If only the old node has children, remove the old node by removeVnode

If both the old and new nodes have children, the updateChildren method is executed.

updateChildren

The diff procedure is implemented primarily in the updateChildren function.

  1. When virtual real DOM DOM rendering of the new and old VNode marked the beginning of the end position, oldStartIdx, newStartIdx, oldEndIdx, newEndIdx

  1. Once the node is marked, it enters the while loop, which exits if the starting position of the old node or the new node is greater than the ending position
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    ...
}
Copy the code
  1. During the cycle, the first and last nodes of the old and new nodes are compared

Specific analysis:

  • First, when the new and old nodes start to meet the requirements of sameVnode, patchVnode is directly used. At the same time, the old and new nodes start to index +1

  • Then compare the end nodes of the old and new nodes. If sameVnode is true, patchVnode is also directly used. At the same time, the new and old nodes end index -1

  • Then the start node of the old node is compared to the end node of the new node. If sameVnode is true, oldStartVnode is moved to the nextSibling of oldEndVnode. PatchVnode of these two nodes is first implemented, and the real DOM node is moved to the back of oldEndVnode by insertBefore. The start index of the old node is +1, but since the old start node is moved behind the old end node, the corresponding end index of the new node needs -1

  • The last comparison is that the end node of the old node is compared with the start node of the new node, which satisfies sameVnode, indicating that oldEndVnode of the old node should be moved before oldStartVnode of the old node. After patchVnode of these two nodes, the current real DOM needs to be moved to the front of oldStartVnode, so corresponding to it, the end index of the old node should be -1, and the start index of the new node should be +1

  1. If none of the four comparisons between the beginning and end of the old and new nodes satisfy sameVnode, the node is searched for reusable nodes based on the key value.

If the new node has a key value, the system starts to search the hash table with oldVnode as the key and corresponding index as value. Otherwise, the search is traversed through the entire tree of old nodes. This also illustrates the importance of defining key values.

Find oldVnode with the same key as newStartVnode from the hash table. If it meets sameVnode, patchVnode is used. At the same time, move the real DOM node to the front of the real DOM corresponding to oldStartVnode.

If newVnode does not exist in the oldVnode queue, call createElm to create a new DOM node in the same location as newStartIdx.

Map table generating functions:

function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key
    if (isDef(key)) map[key] = i
  }
  return map
}
Copy the code

If no key value is defined for the new node, the search starts from the start position of the current old node

function findIdxInOld (node, oldCh, start, end) {
    for (let i = start; i < end; i++) {
      const c = oldCh[i]
      if (isDef(c) && sameVnode(node, c)) return i
    }
  }
Copy the code
  1. After the loop is complete, add or delete nodes according to the number of old and new nodes. If the number of new nodes is greater than the number of old nodes, the extra nodes are created and added to the real DOM; otherwise, the extra nodes are deleted from the real DOM.

How to determine whether two nodes can be reused

Vnode is defined in SRC /core/vdom/vnode.js.

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void; // real context vm for functional nodesfnOptions: ? ComponentOptions;// for SSR caching
  devtoolsMeta: ?Object; // used to store functional render context for devtoolsfnScopeId: ? string;// functional scope id support
  
  constructor(){... }}Copy the code
sameVnode

The sameVnode function is used to determine whether two points can be reused. First, the key of the old node and the new node must be the same. Second, the label must be the same. The input tag makes a special judgment

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
sameInputType
function sameInputType (a, b) {
  if(a.tag ! = ='input') return true
  let i
  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
  return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
Copy the code

Take a look at the definitions of several utility functions: / SRC /shared/utils

export function isUndef (v: any) :boolean %checks {
  return v === undefined || v === null
}

export function isDef (v: any) :boolean %checks {
  returnv ! = =undefined&& v ! = =null
}
Copy the code

Refer to && recommended articles

Vue Diff algorithm parsing

Why doesn’t Vue use index as the key