vue3的diff算法名称是快速diff算法。是在vue2双端diff的基础上增加了最长递增子序列的逻辑。下面一起看下是如何实现的
整体核心思路:复用,能不动就不动,能少移动就少移动,直接创建或者直接删除。
变量说明:
c1 旧节点数组
c2 新节点数组
i 两个数组的前指针 同步向后移动 因此使用一个变量即可标识
e1 旧数组的尾指针 依次向前移动
e2 新数组的尾指针 依次向前移动
前处理
从前往后依次比较节点 直至节点不相等 源码:
css
// 1. sync from start
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
if (isSameVNodeType(n1, n2)) {
patch(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
break
}
i++
}
后处理
从后往前依次比较节点 直至节点不相等 源码:
css
// 2. sync from end
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = (c2[e2] = optimized
? cloneIfMounted(c2[e2] as VNode)
: normalizeVNode(c2[e2]))
if (isSameVNodeType(n1, n2)) {
patch(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
break
}
e1--
e2--
}
仅新增
假如变化前后的两组节点分别如下:仅需要处理新增即可 当i大于e1说明有新增节点 新增[i, e2]的节点内容
源码:
css
// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
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,
(c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i])),
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
i++
}
}
}
仅删除
假如变化前后的两组节点分别如下:仅需要处理删除即可 当i大于e2说明需要删除节点 删除[i, e1]的节点
源码:
css
// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) {
while (i <= e1) {
unmount(c1[i], parentComponent, parentSuspense, true)
i++
}
}
其他case(新增 删除 移动
)
假如经过前面几轮比较后 当前的现状如图所示:
- 新建一个keyToNewIndexMap用于收集c2列表的key:index 到这个map中
typescript
const keyToNewIndexMap: Map<string | number | symbol, number> = new Map()
for (i = s2; i <= e2; i++) {
if (nextChild.key != null) {
keyToNewIndexMap.set(nextChild.key, i)
}
}
-
遍历旧列表c1 在keyToNewIndexMap中找,如果找到了 说明可以被复用, 如果没找到说明当前节点在新列表中不存在 应该被删除。
-
在遍历的过程中需要创建一个newIndexToOldIndexMap的
数组(叫map其实是一个数组)
。数组初始化为0, 0代表节点新增。然后填充这个数组。先用旧的节点在keyToNewIndexMap中找到对应节点在新列表的下标 然后使用旧节点所在的位置 + 1 填充到newIndexToOldIndexMap对应的位置。 -
遍历过旧数组后
删除的case 就已经被处理完毕了
,同时我们得到了newIndexToOldIndexMap数组,记录的是新节点在旧列表中的相对顺序。然后newIndexToOldIndexMap中为 0 的就是需要新增的节点
. -
接下来就是找到需要移动的节点 这里我们需要让节点尽量少移动。所以寻找newIndexToOldIndexMap中的最长递增子序列。在最长子序列中的节点是不需要移动的。
当newIndexToOldIndexMap[i] === 0 说明节点是新增的 需要去挂载dom.
当newIndexToOldIndexMap[i] === increasingNewIndexSequence[j] 说明节点不需要移动。同时移动两个指针。
当newIndexToOldIndexMap[i] !== increasingNewIndexSequence[j] 说明节点需要移动
。
源码 patchKeyedChildren
最长递增子序列的生成方式
欢迎大家点赞收藏评论呦~