我们现在就只需要处理最后一种情况了
我们在 updateChildren.js
在while中
的if最后加个 else
当他们都没哟匹配到的情况
我们现在在updateChildren.js最上面 定义一个空对象 叫 keyMap
参考代码如下
let keyMap = null;
然后 在我们刚写的else中编写代码如下
//判断 如果没有keyMap 表示之前没有处理过
if(!keyMap){
//将keyMap 转成一个对象
keyMap = {};
//循环遍历 条件为旧前加到大于旧后 简单说遍历所有 旧节点的子节点
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
//先存入对应子节点的key
const key = oldch[i].key;
//判断如果是undefined就不要进来了
if (key != undefined) {
keyMap[key] = i;
}
}
}
console.log(keyMap);
newStartIdx++;
简单说 我们判断 如果所有条件都不匹配 就先看看 keyMap中有没有内容 如果没有 就将他设置为一个真的对象 然后 循环所有的旧节点的子节点集合 拿取每一个子节点key 如果key不是undefined 就将key和下标记录给keyMap
最后打印一下结果在控制台 顺手将新前newStartIdx节点加一 因为 如果你不处理 他会死循环的
我们马上来试一下效果
将src下的index.js代码修改如下
import h from "./snabbdom/h";
import patch from "./snabbdom/patch";
const container = document.getElementById("container");
const vnode = h("section", {}, [
h("p", {key:"a"}, "a"),
h("p", {key:"b"}, "b"),
h("p", {key:"c"}, "c"),
h("p", {key:"d"}, "d")
]);
patch( container, vnode)
const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
h("p", {key:"QQQ"}, "QQQ")
]);
btn.onclick = function(){
patch( vnode, vnode1)
}
这样 肯定是不管他什么新前旧前的都匹配不上了 因为我们就一个 QQQ 是旧节点压根没有的
然后我们运行项目
然后点击 更改dom
可以看到 内容全没了 控制台也成功输出了旧节点的key和下标
这里 没了是因为 我们上文写的 while下面那个旧节点删除的逻辑将他们都处理掉了
但现在显然我们要想办法把这个qqq弄上去
我们在这个console.log(keyMap); 后面在加一段代码
//从keyMap中去寻找和新前节点的key相同的节点下标 然后赋值给idxInold
const idxInold = keyMap[newStartVnode.key];
console.log(idxInold);
因为 新前肯定是我们正在处理的这个节点 我们去匹配一下 keyMap 中的内容
如果有和新前key一样的 就取过来 如果没有 我们取到的就是个undefined
我们运行项目 然后点击 更改dom 查看控制台
很显然 他输出了undefined
因为旧节点的子节点中 并没有key为QQQ的子节点
我们可以改一些src下的index.js
将 vnode1 改为
const vnode1 = h("section", {},[
h("p", {key:"c"}, "QQQ")
]);
然后我们运行项目 点击更改dom
可以看到 这次我们就取到了
那么 这个就非常简答了 如果 最后
idxInold不是undefined 表示 旧节点中有这个节点 需要更改一下
如果是undefined 表示 新前就是个全新的节点 需要插入进去
但在这之前 我们要改一下代码
updateChildren.js中的while循环开头加入这样的逻辑代码
//判断如果旧前节点是空的 控制指针后移
if (oldStartVnode == null || oldch[oldStartIdx] == undefined) {
oldStartVnode = oldch[++oldStartIdx];
//判断旧后如果是空的 将旧后向前移一个
} else if (oldEndVnode == null || oldch[oldEndIdx] == undefined) {
oldEndVnode = oldch[--oldEndIdx];
//判断 新前节点如果是空的 则向后移动一个节点
} else if (newStartVnode == null || newCh[newStartIdx] == undefined) {
newStartVnode = newCh[++newStartIdx];
//然后判断 新后节点是空的 向前移动一个节点
} else if (newEndVnode == null || newCh[newEndIdx] == undefined) {
newEndVnode = newCh[--newEndIdx];
}
注意 就和之前的if连在一起 如下图
如果新前 新后 旧前 旧后取不到值 就做一下处理 向前 或向后处理一个节点 保证这个节点是存在的
然后我们在我们刚写的这个
const idxInold = keyMap[newStartVnode.key];
下面 编写代码如下
//判断 如果idxInold是undefined 表示新前是一个全新的节点
if (idxInold == undefined) {
}else{
// 否则表示 旧节点中也有这个节点 需要精细化和移动处理
//先将要处理的老节点存出来
const elmToMove = oldch[idxInold];
//通过patchVNode对新老节点做精细化比较处理
patchVNode(newStartVnode,elmToMove);
//然后在旧节点的集合中将这一项设置为undefined 避免在下面被删除旧节点干掉
oldch[idxInold] = undefined;
//将处理好的节点 移动到 旧前节点的前面
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm);
}
//将新前节点后移
newStartVnode = newCh[++newStartIdx];
这里 我们判断 拿到的idxInold 是不是undefined 就是判断 当前新前节点在旧节点的子节点中是否存在
我们这里 直接先写else 就是 存在 他不为undefined
那么 我们先用elmToMove 记录下旧的这个节点的内容
然后 用patchVNode对他们做精细化比较赋值处理
然后将原本旧节点的这个 赋值为undefined 然后 通过insertBefore 将节点移动到当前旧前
然后 我们下面这个旧节点删除的处理要优化下
简单说 判断一下 确认 oldch[i] 是存在的再继续往下走
然后 我们此时运行项目
点击 更改dom
可以看到 我们 key为c 值为 QQQ的节点就上来了
梳理一下逻辑 首先 第一次
新前新后都是 c
旧前是 a 旧后是 d
此时 四个值都有 所以最前面的几个条件达不到 然后 新前旧前 新后旧后 旧前新后这些都不一样
所以直接走进了最后的else
然后 新前在旧子节点中匹配出了c节点 然后 通过patchVNode做了精细化比较 在将原来旧节点的这个c节点赋值为了undefined
然后 通过insertBefore 将新前(处理了精细化比较的c节点) 移动到目前旧前节点 a 前面
然后 将新前节点 向后推1
因为 新前节点 加一之后就等于1了 而新后节点是接的新节点的子节点长度-1 新节点只有一个子节点
那么 新后就是0 所以 新前大于了新后 循环结束 走人后面两个判断
走进旧节点上传 就将多余部分干掉了
所以 我才叫大家加上那个判断 因为你赋值为undefined之后 他处理完 这个节点在删除循环中是会报错的 要判断 确定有才做删除操作
然后 我们将 src下的index.js代码改成这样
import h from "./snabbdom/h";
import patch from "./snabbdom/patch";
const container = document.getElementById("container");
const vnode = h("section", {}, [
h("p", {key:"a"}, "a"),
h("p", {key:"b"}, "b"),
h("p", {key:"c"}, "c"),
h("p", {key:"d"}, "d")
]);
patch( container, vnode)
const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
h("p", {key:"QQQ"}, "QQQ")
]);
btn.onclick = function(){
patch( vnode, vnode1)
}
然后 我们再运行项目
然后 我们点更改dom
这样就废了
啥都没了 因为我们并没有写这个新增节点的处理
我们在 if (idxInold == undefined) {
下面加入
//通过 createElement 将newStartVnode新前节点变成孤儿节点 然后插入在 oldStartVnode旧前节点前面
parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
然后 我们运行项目
然后点击更改dom
我们的更新就成功了