四种命中方式
- 新前与旧前
- 新后与旧后
- 新后与旧前(命中后-移动节点,移动[新前]指向的[旧节点],移动的[旧后之后])
- 新前与旧后(命中后-移动节点,移动[新前]指向的[旧节点],移动的[旧前之前])
- 命中一点就不再进行命中判断了,如果没命中选择其他方案。
- 如果都没命中,则应该用循环,再旧节点里找,移动位置。
- 如果是旧节点先循环完毕,说明新节点中 有要插入的节点。
- 如果是新节点先循环完毕,如果老节点中 还有剩余节点,说明他们是要被删除的节点。
具体方法
1.声明4个指针
新前,新后,旧前,旧后
2.四个指针指向的节点 3.while循环处理
新前 小于 新后 && 旧前 小于 旧后
4.判断指[针节]点是否未 null 或是 undefined 则跳过节点 5.判断是[四种]命中,是否命中
检查是否未同一个节点(同样的key) 更新节点patchVnode 移动指针
6.[四种]命中都没有命中,制作keyMap一个映射对象,这样就不用每次都遍历老对象了。
找到新前节点(新前在循环中,不断更新)的key,在旧节点中的key 如果是undefined 则证明他是全新项目,插入到全部未处理的最前面 如果不是undefined 则不是全新的项目,则需要移动节点
7.循环结束后,判断start和old的大小
新前小于新后,则遍历newCh,添加到没处理之前 旧前小于旧后,则旧节点未处理完,则要删除旧节点
import createElement from "./createElement.js"
import patchVnode from "./patchVnode.js"
// 新旧节点是否是同一个节点
function checkSameVnode(a, b) {
console.log(a,b, '----------------')
return a.sel === b.sel && a.key === b.key
};
export default function updateElement(parentEle, oldCh, newCh) {
// 旧前
let oldStartInd = 0;
// 新前
let newStartInd = 0;
// 旧后
let oldEndInd = oldCh.length - 1;
// 新后
let newEndInd = newCh.length - 1;
// 旧前节点
let oldStartVnode = oldCh[0];
// 旧后节点
let oldEndVnode = oldCh[oldEndInd];
// 新前节点
let newStartVnode = newCh[0];
// 新后节点
let newEndVnode = newCh[newEndInd];
// 存储未被命中的key
let keyMap = null;
// while循环处理
// 新前 小于 新后 && 旧前 小于 旧后
while (newStartInd <= newEndInd && oldStartInd <= oldEndInd) {
console.log('★')
if (oldStartVnode == null || oldStartVnode == undefined) {
oldStartVnode = oldCh[++oldStartInd]
} else if (oldEndInd == null || oldEndInd == undefined) {
oldEndVnode = oldCh[--oldEndInd]
} else if (newStartInd == null || newStartInd == undefined) {
newStartVnode = newCh[++newStartInd]
} else if (newEndInd == null || newEndInd == undefined) {
newEndVnode = newCh[--newEndInd]
} else if (checkSameVnode(oldStartVnode, newStartVnode)) {
// 检查是否为同一个节点
console.log('1-------------')
// 新前和旧前
console.log('①新前和旧前命中');
patchVnode(oldStartVnode, newStartVnode);
oldStartVnode = oldCh[++oldStartInd]; // 指针先后移,再取值
newStartVnode = newCh[++newStartInd]; // 指针先后移,再取值
} else if (checkSameVnode(oldEndVnode, newEndVnode)) {
console.log('2-------------')
// 新后与旧后
console.log('②新后和旧后命中');
patchVnode(oldEndVnode, newEndVnode);
oldEndVnode = oldCh[--oldEndInd]; // 指针先前移,再取值
newEndVnode = newCh[--newEndInd]; // 指针先前移,再取值
} else if (checkSameVnode(oldStartVnode, newEndVnode)) {
console.log('3-------------')
// 新后与旧前
console.log('③新后与旧前(此种发生了,涉及移动节点,那么新前指向的节点,移动的旧后之后)');
// patchVnode(oldStartVnode, newEndVnode);
/**
* 当③新后与旧前命中的时候,此
* 时要移动节点。移动新前指向的
* 这个节点到老节点的旧后的后面
*/
// 如何移动节点??只要你插入一个已经在DOM树上的节点,它就会被移动
parentEle.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
oldStartVnode = oldCh[++oldStartInd]; // 指针先后移,再取值
newEndVnode = newCh[--newEndInd]; // 指针先前移,再取值
} else if (checkSameVnode(oldEndVnode, newStartVnode)) {
// 新前与旧后
console.log('④新前与旧后(此种发生了,涉及移动节点,那么新前指向的节点,移动的旧前之前)');
patchVnode(oldEndVnode, newStartVnode);
/**
* 当④新前与旧后命中的时候,此
时要移动节点。移动新前指向的
这个节点到老节点的旧前的前面
*/
parentEle.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
// 如何移动节点??只要你插入一个已经在DOM树上的节点,它就会被移动
oldEndVnode = oldCh[--oldStartInd]; // 指针先前移,再取值
newStartVnode = newCh[++newStartInd]; // 指针先后移,再取值
} else {
// 全部未命中
// 寻找新的keyMap
if (!keyMap) {
keyMap = {}
// 缓存旧节点中的key
for (let i = oldStartInd; i <= oldEndInd; i++) {
const key = oldCh[i].key;
// 不等于undefined,则标识未处理
if (key != undefined) {
keyMap[key] = i;
}
}
}
console.log(keyMap);
// 寻找当前这项(newStartIdx)这项在keyMap中的映射的位置序号
const indInOld = keyMap[newStartVnode.key]
console.log(indInOld)
if (indInOld == undefined) {
// 判断,如果idxInOld是undefined表示它是全新的项
// 被加入的项(就是newStartVnode这项)现不是真正的DOM节点
parentEle.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
} else {
// 如果不是undefined,不是全新的项,而是要移动
const elmToMove = oldCh[indInOld]
patchVnode(elmToMove, newStartVnode);
// 把这项设置为undefined,表示我已经处理完这项了
oldCh[indInOld] = undefined;
// 移动,调用insertBefore也可以实现移动。
parentEle.insertBefore(elmToMove.elm, oldStartVnode.elm)
}
// 指针下移,只移动新的头
newStartVnode = newCh[++newStartInd];
}
}
// 继续判断是否有剩余几点,循环结束了start还是比old小
if (newStartInd <= newEndInd) {
console.log('new还有剩余节点没有处理,要加项。要把所有剩余的节点,都要插入到oldStartIdx之前');
// 遍历新的newCh,添加到老的没有处理的之前
for (let i = newStartInd; i <= newEndInd; i++) {
// insertBefore方法可以自动识别null,如果是null就会自动排到队尾去。和appendChild是一致了。
// newCh[i]现在还没有真正的DOM,所以要调用createElement()函数变为DOM
parentEle.insertBefore(createElement(newCh[i]), oldCh[oldStartInd].elm)
}
} else if (oldStartInd <= oldEndInd) {
console.log('old还有剩余节点没有处理,要删除项');
for (let i = oldStartInd; i <= oldEndInd; i++) {
if (oldCh[i]) {
parentEle.removeChild(oldCh[i].elm)
}
}
}
}