双端比较通过定义新旧节点的开始下标与结束下标四个变量。然后进行对比
代码如下
function patchKeyedChildren(n1,n2,container){
const oldChildren = n1.children
const newChildren = n2.children
// 四个索引值
let oldStartIdx = 0
let oldEndIdx = oldChildren.length - 1
let newStartIdx = 0
let newEndIdx = newChildren.length - 1
// 四个索引指向的vnode节点
let oldStartVNode = oldChildren[oldStartIdx]
let oldEndVNode = oldChildren[oldEndIdx]
let newStartVNode = newChildren[newStartIdx]
let newEndVNode = newChildren[newEndIdx]
while(oldStarIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if(oldStartVNode.key === newStartVNode.key){
patch(oldEndVNode,newEndVNode,container)
// 不需要移动dom 移动下标和元素即可
oldStartVNode = oldChildren[++oldStartIdx]
newStartVNode = newChildren[++newStartIdx]
} else if(oldEndVNode.key === newEndVNode.key) {
patch(oldEndVNode,newEndVNode,container)
// 也不需要移动
oldEndVNode = oldChildren[--oldStartIdx]
oldStartVNode = newChildren[--newStartIdx]
} else if(oldStartVNode.key === newEndVNode.key) {
// 进行patch
patch(oldStartVNode,newEndVNode,container)
// 因为旧的开始变为新的结束 因此需要将
insert(oldStartVNode,container,oldEndVNode.el.nextSibing)
// 移动下标 更新元素
oldStartVNode = oldChildren[++oldStartIdx]
} else if(oldEndVNode.key === newStartVNode.key) {
// 先进行patch
patch(oldEndVNode,newStartVNode,container)
// 再进行移动DOM操作
// 因为旧的最后一个变成新的第一个,因为需要移动到第一个元素前面
insert(oldEndVNode.el, container, oldStartVNode.el)
//移动下标和元素
oldEndVNode = oldChildren[--oldEndIdx]
newStartVNode = newChildren[++newStartIdx]
}
}
}
处理在循环内部
在while循环中增加一个else处理在新节点在旧节点的情况
const idxInOld = oldChildren.findIndex(node => node.key === newStartVNode.key)
if(idxInOld > 0) {
patch(oldChildren[idxInOld],newStartVNode,container)
// 将元素移动到最前面
insert(oldChildren[idxInOld].el,container, oldStartVNode.el)
// 由于idxInOld 对应元素已经移动 所以需要将原来的位置对应元素置为undefined
oldChildren[idxInOld] = undefined
newStartVNode = newChildren[++newStartIdx]
} else {
// 将newStartVNode作为新节点挂载到头部,使用当前头部节点 oldStartVNode.el作为锚点
patch(null,newStartVNode,container)
newStartVNode = newChildren[++newStartIdx]
}
因为旧节点可能出现undefined情况,因此需要跳过旧节点为假的情况,需要在循环内最前面添加判断
if(!oldStartVNode) {
oldStartVNode = oldChildren[++oldStartIdx]
} else if(!oldEndVNode) {
oldEndVNode = oldChildren[--oldEndIdx]
}
处理循环外部的新增和删除情况
首先看新增情况,如果新节点开始下标小于新节点结束下标,因此在循环后面增加判断
if(newStartIdx <= newEndIdx && oldStartIdx > oldEndIdx) {
for(let i = newStartIdx; i <= newEndIdx;i++) {
patch(null,newChildren[i],container, oldStartVNode.el)
}
}
接着处理删除情况,如果旧节点开始下标小于旧节点技术下标,需要把多余的节点删除,因此增加判断
else if(oldStartIdx <= oldEndIdx && newStartIdx > newEndIdx) {
for(let i = oldStartIdx; i <= oldEndIdx, i++) {
unmount(oldChildren[i])
}
}