取出新 children 的第一个节点,即 li-c,并尝试在旧 children 中寻找 li-c,结果是我们找到了,并且 li-c 在旧 children 中的索引为 2。
取出新 children 的第二个节点,即 li-a,并尝试在旧 children 中寻找 li-a,也找到了,并且 li-a 在旧 children 中的索引为 0。
递增的趋势被打破了,我们在寻找的过程中先遇到的索引值是 2,接着又遇到了比 2 小的 0,这说明在旧 children 中 li-a 的位置要比 li-c 靠前,但在新的 children 中 li-a 的位置要比 li-c 靠后。这时我们就知道了 li-a 是那个需要被移动的节点,我们接着往下执行
取出新 children 的第三个节点,即 li-b,并尝试在旧 children 中寻找 li-b,同样找到了,并且 li-b 在旧 children 中的索引为 1。
我们发现 1 同样小于 2,这说明在旧 children 中节点 li-b 的位置也要比 li-c 的位置靠前,但在新的 children 中 li-b 的位置要比 li-c 靠后。所以 li-b 也需要被移动。
在当前寻找过程中在旧 children 中所遇到的最大索引值。如果在后续寻找的过程中发现存在索引值比最大索引值小的节点,意味着该节点需要被移动。
javascript复制代码
// 用来存储寻找过程中遇到的最大索引值
let lastIndex = 0
// 遍历新的 children
for (let i = 0; i < nextChildren.length; i++) {
const nextVNode = nextChildren[i]
let j = 0
// 遍历旧的 children
for (j; j < prevChildren.length; j++) {
const prevVNode = prevChildren[j]
// 如果找到了具有相同 key 值的两个节点,则调用 `patch` 函数更新之
if (nextVNode.key === prevVNode.key) {
patch(prevVNode, nextVNode, container)
if (j < lastIndex) {
// 需要移动
} else {
// 更新 lastIndex
lastIndex = j
}
break // 这里需要 break
}
}
}
移动节点
新 children 中的第一个节点是 li-c,它在旧 children 中的索引为 2,由于 li-c 是新 children 中的第一个节点,所以它始终都是不需要移动的,只需要调用 patch 函数更新即可
javascript复制代码
function patchElement(prevVNode, nextVNode, container) {
// 省略...
// 拿到 el 元素,注意这时要让 nextVNode.el 也引用该元素
const el = (nextVNode.el = prevVNode.el)
// 省略...
}
接下来是新 children 中的第二个节点 li-a,它在旧 children 中的索引是 0,由于 0 < 2 所以 li-a 是需要移动的节点,通过观察新 children 可知,新 children 中 li-a 节点的前一个节点是 li-c,所以我们的移动方案应该是:把 li-a 节点对应的真实 DOM 移动到 li-c 节点所对应真实 DOM 的后面
所以我们的思路应该是想办法拿到 li-c 节点对应真实 DOM 的下一个兄弟节点,并把 li-a 节点所对应真实 DOM 插到该节点的前面
javascript复制代码
// 用来存储寻找过程中遇到的最大索引值
let lastIndex = 0
// 遍历新的 children
for (let i = 0; i < nextChildren.length; i++) {
const nextVNode = nextChildren[i]
let j = 0
// 遍历旧的 children
for (j; j < prevChildren.length; j++) {
const prevVNode = prevChildren[j]
// 如果找到了具有相同 key 值的两个节点,则调用 `patch` 函数更新之
if (nextVNode.key === prevVNode.key) {
patch(prevVNode, nextVNode, container)
if (j < lastIndex) {
// 需要移动
// refNode 是为了下面调用 insertBefore 函数准备的
// 拿到新节点列表的上一个节点,插到其后面
const refNode = nextChildren[i - 1].el.nextSibling
// 调用 insertBefore 函数移动 DOM
container.insertBefore(prevVNode.el, refNode)
} else {
// 更新 lastIndex
lastIndex = j
}
break // 这里需要 break
}
}
}
添加新元素
节点 li-d 在旧的 children 中是不存在的,所以当我们尝试在旧的 children 中寻找 li-d 节点时,是找不到可复用节点的,这时就没办法通过移动节点来完成更新操作,所以我们应该使用 mount 函数将 li-d 节点作为全新的 VNode 挂载到合适的位置。