4

Like React, Vue also has its own diff algorithm-patch when dealing with virtual DOM updates.

What is patch

When Vue renders the DOM through the VNode node, it does not use the current VNode node to violently update the DOM node, but compares the new and old VNode nodes through the patch algorithm, and then finds the different attributes or nodes by comparing the results. Need to be more detailed. Obviously, patch can reduce unnecessary overhead and improve performance.

The patch process mainly completes the following things:

  • Create new nodes that need to be added
  • Remove deprecated nodes
  • Move or modify the node that needs to be updated

Vue3's patch optimization-patchFlag

In Yuda's many speeches on Vue3, patchFlag's optimization of patch has been mentioned, so when reading the source code, the author also pays special attention to the part of patchFlag. So what is the use of patchFlag?

For example, when two nodes are of the same type, although the two nodes are of the same type, the attributes of the new node may also have changed, so at this time we also need to traverse the attributes of the node to effectively determine whether need to be updated.

Then we look at the following elements:

<div id="bar" :class="foo">Hello World</div>

For such a div element, it can be clearly judged from the front-end engineer's point of view that id="bar" and children are both static attributes and will not change anymore. You only need to pay attention to whether the class changes. Now Vue3 uses patchFlag to do this. After the AST tree is generated, when each node is traversed through the converter, the corresponding patchFlag will be marked according to the characteristics of the node. In the patch process, only the class props will be processed instead of full comparison. In this way, the number of times that props are traversed can be reduced and the performance can be improved.

How does patch work

After understanding the role of patch, I will take everyone to take a look at the source code implementation of patch. The source code of patch is located in the file @runtime-core/src/renderer.ts. Since the code length in this file is very long, when explaining the code, the author will omit non-essential code and only focus on the main logic.

Because the author hopes that what you can gain from reading the article is the logical summary in the patch process, as well as paying attention to the key logic. Not everyone needs to read the source code, and some key codes are posted in the hope that when students are interested, they can have a code snippet to refer to when they want to read the source code.

const patch: PatchFn = (
  n1,
  n2,
  container,
  anchor = null,
  parentComponent = null,
  parentSuspense = null,
  isSVG = false,
  slotScopeIds = null,
  optimized = false
) => {
  // patching & 不是相同类型的 VNode,则从节点树中卸载
  if (n1 && !isSameVNodeType(n1, n2)) {
    anchor = getNextHostNode(n1)
    unmount(n1, parentComponent, parentSuspense, true)
    n1 = null
  }
    // PatchFlag 是 BAIL 类型,则跳出优化模式
  if (n2.patchFlag === PatchFlags.BAIL) {
    optimized = false
    n2.dynamicChildren = null
  }

  const { type, ref, shapeFlag } = n2
  switch (type) { // 根据 Vnode 类型判断
    case Text: // 文本类型
      processText(n1, n2, container, anchor)
      break
    case Comment: // 注释类型
      processCommentNode(n1, n2, container, anchor)
      break
    case Static: // 静态节点类型
      if (n1 == null) {
        mountStaticNode(n2, container, anchor, isSVG)
      }
      break
    case Fragment: // Fragment 类型
      processFragment(/* 忽略参数 */)
      break
    default:
      if (shapeFlag & ShapeFlags.ELEMENT) { // 元素类型
        processElement(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
      } else if (shapeFlag & ShapeFlags.COMPONENT) { // 组件类型
        processComponent(/* 忽略参数 */)
      } else if (shapeFlag & ShapeFlags.TELEPORT) { // TELEPORT 类型
        ;(type as typeof TeleportImpl).process(/* 忽略参数 */)
      }
  }
}

Follow the author to look at the source code of the patch function above. Let's start with the reference: n1 and n2 are the two nodes to be compared, n1 is the old node, and n2 is the new node. The container is the container of the new node, and the anchor is an anchor point, which is used to identify which node is the reference object when we add, delete or move the old and new nodes. The optimized parameter is the identification of whether to open the optimization mode. The other parameters will not be introduced one by one, we don't need to care about it for the time being.

Let's look at the first if condition. When the old node exists and the new and old nodes are not of the same type, the old node will be unloaded from the node tree. This is the first logic of patch that we can summarize: When the two nodes are of different types, the old node will be uninstalled directly.

Looking at the second if branch condition, if the value of patchFlag of the new node is BAIL, the optimization mode will be turned off. This is the first time we have encountered patchFlag in the source code, but we don't need to go into it too much, let's look down.

Next, the patch function will determine the node type through switch case, and perform different operations on different node types.

Here we take the element type of ELEMENT as an example, because all kinds of HTML elements are the types that we deal with most of the time. In the source code above, when the case goes to the default branch, the current is determined by the bitwise AND operation of shapeFlag. VNode is an element type, and then call processElement to continue the patch process, and pass the parameters in the patch function, and I will continue to analyze it along the processElement function.

Patch process of the element-processElement

The code logic of the processElement function itself is very simple, to sum it up in one sentence: if there is an old node, continue to compare the old and new nodes through the patch, otherwise directly mount the new node. The source code is as follows:

const processElement = (
  n1: VNode | null,
  n2: VNode,
  container: RendererElement,
  anchor: RendererNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  slotScopeIds: string[] | null,
  optimized: boolean
) => {
  // 如果旧节点不存在
  if (n1 == null) {
    mountElement(
      n2,
      container,
      anchor
      /* 后续参数省略 */
    )
  } else {
    patchElement(
      n1,
      n2,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  }
}

It is relatively simple to mount directly when the old node does not exist, so in order to reflect the process of the patch, the author will assume that both the old and new nodes exist, and continue to analyze along the patchElement.

Compare child nodes-patchElement

In the patch process of the element type, Vue3 first extracts the prop declarations of the new and old nodes, because the props need to be compared later.

Before the comparison starts, some hooks are triggered, such as the hook of VNode itself: onVnodeBeforeUpdate, and the hook beforeUpdate of the instruction bound on the element.

if ((vnodeHook = newProps.onVnodeBeforeUpdate)) {
  invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
}
if (dirs) {
  invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
}

Update attributes

Then start to compare props. If the element is marked with patchFlag at this time, it will be compared on-demand through patchFlag, otherwise the props in the diff element will be full.

if (patchFlag > 0) {
  if (patchFlag & PatchFlags.FULL_PROPS) {
    // 如果元素的 props 中含有动态的 key,则需要全量比较
    patchProps(
      el,
      n2,
      oldProps,
      newProps,
      parentComponent,
      parentSuspense,
      isSVG
    )
  } else {
    if (patchFlag & PatchFlags.CLASS) {
      if (oldProps.class !== newProps.class) {
        hostPatchProp(el, 'class', null, newProps.class, isSVG)
      }
    }

    if (patchFlag & PatchFlags.STYLE) {
      hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
    }

    if (patchFlag & PatchFlags.PROPS) {
      const propsToUpdate = n2.dynamicProps!
      for (let i = 0; i < propsToUpdate.length; i++) {
        const key = propsToUpdate[i]
        const prev = oldProps[key]
        const next = newProps[key]
        if (
          next !== prev ||
          (hostForcePatchProp && hostForcePatchProp(el, key))
        ) {
          hostPatchProp(
            el,
            key,
            prev,
            next,
            isSVG,
            n1.children as VNode[],
            parentComponent,
            parentSuspense,
            unmountChildren
          )
        }
      }
    }
  }

  if (patchFlag & PatchFlags.TEXT) {
    if (n1.children !== n2.children) {
      hostSetElementText(el, n2.children as string)
    }
  }
} else if (!optimized && dynamicChildren == null) {
  patchProps(
    el,
    n2,
    oldProps,
    newProps,
    parentComponent,
    parentSuspense,
    isSVG
  )
}

Let's take a look at the branch conditions above and see what patchFlag does at this time.

  • When patchFlag is FULL_PROPS, it means that the element at this time may contain a dynamic key, and full props diff is required.
  • When patchFlag is CLASS, when the classes of the new and old nodes are inconsistent, the class will be patched at this time, and when the class attributes of the old and new nodes are exactly the same, no operation is required. This Flag will be added when the element has dynamic class binding.
  • When patchFlag is STYLE, the style will be updated. This is done every time the patch is executed. This Flag will be added when there is a dynamic style binding.
  • When patchFlag is PROPS, it should be noted that this Flag will be added when the element has dynamic attributes or attrs binding. Unlike class and style, the keys of these dynamic props or attrs will be saved for faster iteration.

    • The PROPS comparison will extract the dynamic attributes of the new node and traverse all the keys in this attribute. When the old and new attributes are inconsistent or the key needs to be updated forcibly, hostPatchProp is called to update the attributes.
  • When patchFlag is TEXT, if the text of the child node in the old and new node changes, call hostSetElementText to update. This flag will be added when the child nodes of the element only contain dynamic text.

At this point, the branch judgment when the element has patchFlag is over. We can experience the efforts of patchFlag to improve the speed of the patch algorithm in these branch judgments.

The branch goes to the last else. If there is no optimization mark and no dynamic sub-nodes currently exist, then a full diff of the props will be performed directly, which is done through the patchProps function.

For the comparison of props, due to space limitations, we will only talk about it here, and the specific function calls in the branch will not be discussed.

Update child nodes-patchChildren

Next, we will enter the most important part of updating child nodes. During the patch process of the element, it will be judged whether there are dynamic child nodes. If so, patchBlockChildren will be called to update only the dynamic child nodes, otherwise patchChildren will be called to update the child nodes in full.

Here we will look at the general situation: patchChildren.

When updating child nodes, firstly use the ability of patchFlag to classify child nodes and make different treatments. In order to avoid large-length posting of source code, here I will summarize the logic of patchChildren function for everyone, and then we will focus on it. The general part.

  • Judge according to patchFlag:

    • If patchFlag is a Fragment with key value: KEYED_FRAGMENT, call patchKeyedChildren to continue processing child nodes.
    • If patchFlag is a Fragment with no key value: UNKEYED_FRAGMENT, then patchUnkeyedChildren is called to handle the child nodes without a key value.
  • Judge according to shapeFlag (element type flag):

    • If the new child node is of text type and the old child node is of array type, the child nodes of the old node are directly uninstalled.

      • If the types of the new and old nodes are the same, the text of the new child node is directly updated.
    • If the old child node type is an array type

      • If the new child node is also an array type, call patchKeyedChildren to perform a complete diff.
      • If the new child node is not an array type, it means that there is no new child node, and you can simply uninstall the old node from the tree.
    • If the old child node is of text type, since it has been judged whether the new child node is of text type at the beginning, then you can be sure that the new child node is definitely not of text type, you can directly set the text of the element to an empty string .
    • If the new child node is an array type, and the old child node is not an array, it means that the new child node needs to be mounted in the tree and the mount operation is sufficient.

The above is the complete logic of patchChildren. The most complicated logic here is to call patchKeyedChildren to perform a complete comparison of the two arrays when the old and new child nodes are both array types.

Update strategy for child nodes

How to do the diff algorithm efficiently, the most important performance bottleneck is how to compare the child nodes in the tree more quickly to figure out what specific operations need to be performed. If you compare them according to normal thinking, the time complexity is at least O(n ^3), then when a tree with 1000 child nodes is compared, at least 1 billion comparisons are required, so this operation is undoubtedly very expensive. For this situation, how to implement an algorithm with a time complexity of O(n) is a problem that the front-end framework must consider. Like React, Vue also has keys to help identify child nodes, helping the framework to identify and compare child nodes more efficiently.

Vue2's child node optimization strategy

When I read the book "Vue.js", the author Liu Bowen summarized the sub-node optimization strategies of Vue2 into four categories:

  • New Front and Old Front
  • New Queen and Old Queen
  • After the new and before the old
  • New before and after

It should be noted that the new front refers to the node whose new child node index is at the top, the new back refers to the node whose new child node index is at the end, and the old refers to the old child node.

At that time, after reading this book, I felt that this summary was very appropriate and memorable, and it also helped me to read the source code of Vue2 more easily and understandably, so I still intend to use this name in the sub-node update strategy of Vue3. Describe the update strategy.

Vue3 child node update

For the fluency of the description and to be able to see the code at any time for reference, I will split the patchKeyedChildren function into logical points one by one, and paste the source code and corresponding pictures according to the rhythm of our description.

const patchKeyedChildren = (
  c1: VNode[],
  c2: VNodeArrayChildren,
  container: RendererElement,
  parentAnchor: RendererNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  slotScopeIds: string[] | null,
  optimized: boolean
) => {
  let i = 0
  const l2 = c2.length
  let e1 = c1.length - 1 // prev ending index
  let e2 = l2 - 1 // next ending index
  /* 忽略后续逻辑 */
}

First, let's look at the function signature part of patchKeyedChildren. The c1 and c2 in the function parameters represent the old child node and the new child node respectively.

And the function will declare 4 variables at the beginning:

  • Traverse the index of the child node i = 0
  • New child node length: l2
  • End index of the old child node: e1
  • The end index of the new child node: e2

After remembering these four declared variables, we start to look at the first comparison strategy of the word sub-node in Vue3.

New Front and Old Front

patch-1.jpg

Looking at this picture first, the C1 and C2 nodes in the same row are the two new and old child nodes to be compared. The comparison child node will start from the starting index of the two child nodes:

When i = 0, compare the 0th index and find that the A node of C1 and the A node of C2 are the same type of element, then the new and old A nodes will be patched, and the patch can be used to access A recursively. For all child nodes under the node, the index i is incremented after the patch is completed.

Continuing the comparison, it is found that the B node of C1 and the B node of C2 are also elements of the same type, and the i index is incremented after patching the B node as before.

When comparing the third child node, you will find that the C node of C1 and the D node of C2 are not the same type of node, so it will break out of the cycle of comparison between the new front and the old front, so the comparison between the new front and the old front ends.

At this time, the comparison of the first two nodes of the child nodes of C1 and C2 has been completed. This comparison process is like this in the source code:

while (i <= e1 && i <= e2) {
  const n1 = c1[i]
  const n2 = (c2[i] = optimized
    ? cloneIfMounted(c2[i] as VNode)
    : normalizeVNode(c2[i]))
  // 比较 n1 与 n2 是否是同一类型的 VNode
  if (isSameVNodeType(n1, n2)) {
    patch(
      n1,
      n2,
      container,
      null,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else {
    // 如果 n1 与 n2 不是同一类型,则 break 出 while 循环
    break
  }
  // 递增 i
  i++
}

New Queen and Old Queen

After the comparison between the new front and the old front is completed, consistent with the optimization strategy in Vue2, the comparison between the new front and the old front will start.

patch-2.jpg

Let's take a look at the sample pictures of the new queen and the old queen together. I believe that after the introduction of the new front and the old front, everyone can already understand the comparison between the new queen and the old queen.

The elements are compared starting from the end by the end indexes of the two new and old child nodes declared before e1 and e2.

According to the comparison in the figure, the logic is as follows:

  • Starting from the end, C1 is the C node, and C2 is also the C node. The two nodes are of the same type, and the patch comparison is started. After the patch is completed, the end index of the new and old child nodes is -1.
  • For the second comparison, the end of C1 is node B, and the end of C2 is node B. The types are the same, and the patch is performed, and then the tail index is decremented.
  • For the third comparison, the end node of C1 is A, and the end node of C2 is E. The types are different. Break jumps out of the comparison cycle between the new and the old ones.

The following is the comparison source code of the new and old ones:

while (i <= e1 && i <= e2) {
  const n1 = c1[e1]
  const n2 = (c2[e2] = optimized
    ? cloneIfMounted(c2[e2] as VNode)
    : normalizeVNode(c2[e2]))
  // 比较 n1 与 n2 是否是同一类型的 VNode
  if (isSameVNodeType(n1, n2)) {
    patch(
      n1,
      n2,
      container,
      null,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else {
    // 如果 n1 与 n2 不是同一类型,则 break 出 while 循环
    break
  }
  // 完成 patch 操作后,尾部索引递减
  e1--
  e2--
}

Mounting of new child nodes in regular order

When we have completed the first two rounds of comparison, at this time, we can often find some new sub-nodes in the regular sequence number, but the old sub-nodes do not have elements. At this time, these new sub-nodes need to be inserted.

patch-3.jpg

As shown in the figure, when the comparison between the new front and the old front is completed, the index i has increased by more than the length of the child node of C1. At this time, i = 2, and i is less than or equal to the length of the child node of C2, so it can be determined that the new There are still nodes in the child nodes that have not been traversed. At this time, the old child nodes have all been traversed, so insert all the child nodes that have not been traversed.

Corresponding to this picture, the C node should be inserted.

The source code of this mounting process is as follows, please refer to the comments in the source code:

// 当旧子节点被遍历完
if (i > e1) {
  // 新节点还有元素未被遍历完
  if (i <= e2) {
    const nextPos = e2 + 1
    // 确定好锚点元素
    const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
    // 遍历剩余的新子节点
    while (i <= e2) {
        // patch 时第一个参数传入 null,代表没有旧节点,直接将新节点插入即可
      patch(
        null,
        (c2[i] = optimized
          ? cloneIfMounted(c2[i] as VNode)
          : normalizeVNode(c2[i])),
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
      i++
    }
  }
}

Remove redundant nodes in regular order

Corresponding to the above mounting logic is to remove redundant old child nodes.

When the new child nodes have all been traversed, if the old child nodes still have elements that have not been traversed at this time, then it can be determined that the remaining old child nodes are no longer needed, so the remaining old child nodes can be removed directly.

patch-4.jpg

From the figure, after the comparison between C1 and C2 is completed, the old child node C1 still has node A remaining, and there are no more nodes to compare in the new node, so directly remove node A That's it.

The corresponding logic in the source code is also very simple:

// 如果新子节点已被遍历完
else if (i > e2) {
  // 就子节点未被遍历完
  while (i <= e1) {
    // 调用 unmount 卸载旧子节点
    unmount(c1[i], parentComponent, parentSuspense, true)
    // 递增索引
    i++
  }
}

Comparison of child nodes in unknown order

After the above ideal situation processing is completed, we will have to face the remaining scene where the position of the element cannot be determined.

patch-5.jpg

Let’s take a look at this picture first. You can see that there are more elements mounted in the new and old child nodes. When we compare the new front with the old front, and the new back with the old back, there is still uncertainty in the middle part. The order of the nodes is up. CDE in the old child node, EDCH in the new child node.

As a bystander, we are very clear that at this time we need to move the CDE and insert the H node. Let's take a look at how patch accomplishes this task:

  • Declare two variables s1 and s2, and assign the pre-order index i traversed at this time to s1 and s2. s1 and s2 respectively represent the starting index of the new and old child nodes.
  • Taking s2 as the starting node and e2 as the ending condition, traverse the new child node, using the key of the child node in the new child node as the key and the index i as the value to generate a Map object and store the original index.

    • If there are duplicate keys found in the child nodes at this time, a warning familiar to all Vue developers will be issued: Duplicate keys found during update xxx, Make sure keys are unique.
    const s1 = i // 旧子节点的起始索引
    const s2 = i // 新子节点的起始索引
    
    // 对新子节点,创建一个索引的 map 对象
    const keyToNewIndexMap: Map<string | number, number> = new Map()
    for (i = s2; i <= e2; i++) {
      const nextChild = (c2[i] = optimized
        ? cloneIfMounted(c2[i] as VNode)
        : normalizeVNode(c2[i]))
      if (nextChild.key != null) {
        // 如果是 DEV 环境,且 keyToNewIndexMap 已经存在当前节点的 key 值,则警告。
        if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {
          warn(
            `Duplicate keys found during update:`,
            JSON.stringify(nextChild.key),
            `Make sure keys are unique.`
          )
        }
        // 以新子节点的 key 为键,索引为值,存入 map。
        keyToNewIndexMap.set(nextChild.key, i)
      }
    }
  • Declare the variable toBePatched, and calculate how many nodes need to be patched. Declare the variable patched = 0 and record the number of nodes in the patch.
  • Declare an array of newIndexToOldIndexMap to determine the longest incremental subsequence later. The size of the newIndexToOldIndexMap array is the length of toBePatched, and all elements in the array are initialized to 0.

    • newIndexToOldIndexMap, in the form of Map<newIndex, oldIndex>
    • It should be noted that the oldIndex stored in it has an index offset of +1.
    • oldIndex = 0 is a special value, which means that there is no corresponding old child node in the new child node.
  • Traverse the old child nodes and mark the child node currently being traversed as prevChild.

    • If patched is greater than or equal to toBePatched, indicating that all nodes that need to be patched have been compared, the remaining prevChild can be removed.
    • Otherwise declare the variable newIndex.
    • If the key of prevChild is not empty, take the value of prevChild.key from keyToIndexMap and assign the obtained value to newIndex.
    • If newIndex has no value, it means that there is no corresponding old child node in the new child node, and the old child node of prevChild is directly removed.
    • Otherwise, save the new index in newIndexToOldIndexMap, mark the farthest position of the current index movement or add the movement mark, and compare the old and new child nodes in patch.
    • After completing the patch, increment the patched count.

The code for the above logic is as follows:

/**
 * 遍历旧子节点,尝试 patch 比较需要被 patch 的节点,并且移除不会再出现的子节点
 */
let j
let patched = 0
const toBePatched = e2 - s2 + 1
let moved = false
// 用于跟踪是否有节点发生移动
let maxNewIndexSoFar = 0
// 用于确定最长递增子序列
const newIndexToOldIndexMap = new Array(toBePatched)
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0

for (i = s1; i <= e1; i++) {
  const prevChild = c1[i]
  if (patched >= toBePatched) {
    // 所有新节点都被 patch 了,所以剩下的只需要移除
    unmount(prevChild, parentComponent, parentSuspense, true)
    continue
  }
  let newIndex
  if (prevChild.key != null) {
    newIndex = keyToNewIndexMap.get(prevChild.key)
  } else {
    // 对于找不到 key 的节点,尝试去定位相同 type 的节点
        /* 忽略逻辑 */
  }
  // 如果旧子节点不能匹配到对应的新子节点,则移除该旧子节点
  if (newIndex === undefined) {
    unmount(prevChild, parentComponent, parentSuspense, true)
  } else {
    // 在 newIndexToOldIndexMap 记录下被 patch 的节点的索引
    newIndexToOldIndexMap[newIndex - s2] = i + 1
    // 如果 newIndex 的索引大于最远移动的索引,则更新
    if (newIndex >= maxNewIndexSoFar) {
      maxNewIndexSoFar = newIndex
    } else {
      // 否则标记 moved 为 true
      moved = true
    }
    // 对新旧子节点进行 patch
    patch(
      prevChild,
      c2[newIndex] as VNode,
      container,
      null,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
    // patch 完毕后,递增 patched 计数。
    patched++
  }
}

After traversing the old child nodes, we have removed unnecessary old child nodes, and the corresponding old child nodes that still exist in the new node are all patched. Next, we need to traverse the new child nodes, just Only the parts that need to be patched are traversed from back to front. The purpose is to insert new child nodes and move the child nodes that need to be moved at the same level. The logic is described as follows:

  • If there is a moved tag, find the longest increasing subsequence from newIndexToOldIndexMap, and assign j to the end index of the longest increasing subsequence array.
  • Traverse the new child node from back to front, so that we can determine the position of the anchor element.
  • Declare newIndex = s2 + i, which is the last node that needs to be patched.
  • Get the anchor element.
  • If this node needs to be patched, the value of i index in newIndexToOldIndexMap is 0. Remember what the author suggested before, 0 is a special value, which means that the node has no corresponding node in the old child node. Then for the element that does not have a corresponding node, we will use the insert operation on it.
  • If there is a corresponding index in newIndexToOldIndexMap, but there is a moved mark, it means that the node may move and the judgment should be continued.

    • If j <0, it means that all nodes in the longest increasing subsequence have been processed. Or when the index i is not equal to the value corresponding to the index j in the longest growing subsequence, it means that the node is not in a relatively stable position, and a move operation is required.
    • If the above conditions are met, the j index is decremented, and the node is not processed.

The above logic code is as follows:

/**
 * 移动和挂载
 */
// 当节点被移动时,创建最长递增子序列
const increasingNewIndexSequence = moved
  ? getSequence(newIndexToOldIndexMap)
  : EMPTY_ARR
j = increasingNewIndexSequence.length - 1
// 为了能方便的获取锚点,选择从后向前遍历
for (i = toBePatched - 1; i >= 0; i--) {
  const nextIndex = s2 + i
  const nextChild = c2[nextIndex] as VNode
  const anchor =
    nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
  if (newIndexToOldIndexMap[i] === 0) {
        // 如果在 newIndexToOldIndexMap 中找不到对应的索引,则新增节点
    patch(
      null,
      nextChild,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else if (moved) {
    // 如果不是一个稳定的子序列,或者当前节点不在递增子序列上时,需要移动
    if (j < 0 || i !== increasingNewIndexSequence[j]) {
      move(nextChild, container, anchor, MoveType.REORDER)
    } else {
      j--
    }
  }
}

At this point, the update of the child nodes with key values is over. Follow the author to look at it. If you can carefully combine the source code and logic to look at it, then I believe it is very easy to understand the source code of the child node update.

to sum up

In this article, the author introduced the role of patch, and also explained that Vue3 uses patchFlag to speed up performance optimization when updating nodes.

After that, the author explained the flow of the patch function, and took the element ELEMENT type as an example to analyze the call flow of the function until the update of the child node was introduced.

In the update of child nodes, we compared the update strategies of Vue2 and Vue3, and explained in detail how Vue3 compares child nodes with key values.

Here I can summarize the sub-node update of Vue3 into 5 steps:

1. The comparison between the new and the old.

2. The comparison between the new post and the old post.

3. New operations for regular sequences.

4. The removal operation of the regular sequence.

5. Processing of unknown sequence.

5.1 对新子节点建立 key 与索引的 map:keyToNewIndexMap 。

5.2 遍历旧子节点对能够匹配到的旧节点进行 patch 操作,对于不会存在的旧子节点进行移除操作。

5.3 对新子节点进行移动或新增操作。

The above 5 points are the very important sub-node update process in Vue3. If you think the article is too long to read, friends can also read the summary directly.

Finally, if this article can help you understand the patch process and child node update strategy in Vue3, I hope to give this article a little like ❤️. If you want to continue to follow up the follow-up articles, you can also follow my account or follow my github , thank you again for the lovely masters.


Originalix
165 声望63 粉丝

前端工程师,欢迎来 github 互相 follow, originlaix