vue3 虚拟dom与diff算法(小满zs vue3 笔记五)
diff算法(虚拟dom会生成两个节点如c1,c2)
图1
vue3 diff算法原码地址: https://github.com/vuejs/core
1. diff 算法主要是说renderer.ts中patchChildren这段代码逻辑,如下:
2. diff算法排序分为无key时diff算法排序逻辑和有key时diff算法排序逻辑
2.1 无key时diff算法排序逻辑, 分为三步如下,如图1中无key:
* 通过for循环patch重新渲染元素,来替换
* 删除
* 新增
const patchUnkeyedChildren = ( c1: VNode[], c2: VNodeArrayChildren, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { c1 = c1 || EMPTY_ARR c2 = c2 || EMPTY_ARR const oldLength = c1.length const newLength = c2.length const commonLength = Math.min(oldLength, newLength) let i for (i = 0; i < commonLength; i++) { const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as VNode) : normalizeVNode(c2[i])) //1. 循环patch替换 patch( c1[i], nextChild, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } //2. 删除 if (oldLength > newLength) { // remove old unmountChildren( c1, parentComponent, parentSuspense, true, false, commonLength ) } else { //3. 新增 // mount new mountChildren( c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, commonLength ) } }
2.2 有key时diff算法排序逻辑分为五步如下:
* 前序算法前面元素与前面元素比较如 isSameVNodeType,如果不一样,跳出循环
* 尾序算法尾和尾比较,如果不一样,跳出循环
* 新节点如果多出来就挂载(新增)
* 旧节点如果多出来就卸载(删除) ,前4点如图1 有key
* 乱序(涉及最长递增子系列算法),3个点
* 构建新节点的映射关系
例子:
key 1 2 3 4 5
index 0 1 2 3 4
// sort:
key 5 4 3 2 1
index 0 1 2 3 4
5 -> 0 4 -> 1 3 -> 2 2 -> 3 1 -> 4
* 记录新节点在旧节点的位置(newIndexToOldIndexMap方法),如果多余点删除(unmount),如果新节点不包含旧节点也删除,节点出现交叉,做移动标记,求最长递增算法
// 5.2 loop through old children left to be patched and try to patch // matching nodes & remove nodes that are no longer present let j let patched = 0 const toBePatched = e2 - s2 + 1 let moved = false // used to track whether any node has moved let maxNewIndexSoFar = 0 // works as Map<newIndex, oldIndex> // Note that oldIndex is offset by +1 // and oldIndex = 0 is a special value indicating the new node has // no corresponding old node. // used for determining longest stable subsequence // 记录新节点在旧节点的位置数组 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) { // all new children have been patched so this can only be a removal // 如果多余节点删除 unmount(prevChild, parentComponent, parentSuspense, true) continue } let newIndex if (prevChild.key != null) { newIndex = keyToNewIndexMap.get(prevChild.key) } else { // key-less node, try to locate a key-less node of the same type for (j = s2; j <= e2; j++) { if ( newIndexToOldIndexMap[j - s2] === 0 && isSameVNodeType(prevChild, c2[j] as VNode) ) { newIndex = j break } } } // 如果新节点不包含旧节点也删除 if (newIndex === undefined) { unmount(prevChild, parentComponent, parentSuspense, true) } else { newIndexToOldIndexMap[newIndex - s2] = i + 1 if (newIndex >= maxNewIndexSoFar) { maxNewIndexSoFar = newIndex } else { // 节点出现交叉,做移动标记,求最长递增算法 moved = true } patch( prevChild, c2[newIndex] as VNode, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) patched++ } }
* 求最长递增(升序)算法排序(图1 最长递增算法)
最长递增算法: 1. 选默认递增序列如图1 乱序dp 1 1... 2. 比较 10 -> 1 , 9 -> 9比10小 1 ,2 -> 2比9,10小 1, 5 -> 5比9,10,2 比2大,所以1基础上加1 2,3 -> 比同上,比2大 2, 7 -> 比10..5..3比5大(5对应的是2,加1) 3, 101 -> 比10..7都大取最大值加1 4, 18 -> 比10..101 同上 4 3. 对就索引还是0 1 2 3....
代码事例:
<template> <div> <div :key="item" v-for="(item) in Arr">{{ item }}</div> </div> </template> <script setup lang="ts"> const Arr: Array<string> = ['A', 'B', 'C', 'D'] Arr.splice(2,0,'DDD') </script> <style> </style>
参考文章: https://xiaoman.blog.csdn.net/article/details/122778560?spm=1001.2014.3001.5502