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重新渲染元素,来替换
* 删除
* 新增
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 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个点
* 构建新节点的映射关系
1 2 3 4 5 6 7 | 例子: 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 |
1 | * 记录新节点在旧节点的位置(newIndexToOldIndexMap方法),如果多余点删除(unmount),如果新节点不包含旧节点也删除,节点出现交叉,做移动标记,求最长递增算法 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | // 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....
代码事例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <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
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战