上文
vue diff算法与虚拟dom知识整理(13) 手写patch子节点更新换位策略
我们实现了子节点位置的更新策略
但还有一些匹配不到的情况会导致死循环 那么我们继续来优化一下
我们先将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")
]);
patch( container, vnode)
const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
h("p", {key:"a"}, "a"),
h("p", {key:"b"}, "b"),
h("p", {key:"c"}, "c"),
h("p", {key:"d"}, "d")
]);
btn.onclick = function(){
patch( vnode, vnode1)
}
我们先来处理 在后面多加入一个节点的逻辑
首先 我们这样写 现在肯定是没有效果的 因为我们并没有写新增的逻辑
我们打开案例中的updateChildren.js
在最上面引入一下createElement
import createElement from "./createElement";
然后 我们在 updateChildren.js 的最后面 加入这段代码
//查看是否还有剩余节点
//判断处理后 新前是否还是比新后小或者二者相同
if (newStartIdx <= newEndIdx) {
//定义before 复制微新后节点 如果拿不到新后 就直接赋值为unll 因为 insertBefore第二个标杆节点参数给null他就会将节点插入在最后面
const before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
//开启循环 i等于没做完的新前节点 只要i还小于或者等于新后旧继续循环
for(let i = newStartIdx ; i <= newEndIdx; i++) {
//用createElement将 newCh(新虚拟节点的集合) 的第i个下标从虚拟节点准成真的个人节点 并插入在before节点的前面
parentElm.insertBefore(createElement(newCh[i]),before);
}
}
加到 while 循环的后面
我们简单判断 如果循环都已经结束了 如果新前还没有加到比新后大 说明 我们的新节点的子节点并没有处理完
我们先获取新后节点
然后 循环遍历 将剩余的新节点都插入到新后后面去
这里 写了个三元运算符 当新后拿不到是 赋值为unll 意思就是 利用了insertBefore第二个参数 如果是unll就自动将元素插在最后面的特性
还有就是createElement 因为 你现在遍历的是虚拟节点 所以 要通过createElement 将虚拟节点变为孤儿节点 然后 插入到dom后面
然后 我们运行项目
点击更改dom 会发现 我们的d节点就上去了
但是 如果现在 我们旧节点 如果大于新节点
例如 旧节点的 子节点为 a b c d 新节点 为 a b c 不用试了 我已经帮你们试过了 功能肯定是实现不了的
我们接下来 来处理一下删除的操作
我们先将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"),
h("p", {key:"e"}, "e")
]);
patch( container, vnode)
const btn = document.getElementById("btn");
const vnode1 = h("section", {},[
h("p", {key:"a"}, "a"),
h("p", {key:"b"}, "b"),
h("p", {key:"c"}, "c"),
h("p", {key:"e"}, "e")
]);
btn.onclick = function(){
patch( vnode, vnode1)
}
暂时 我们点击肯定是没有什么效果的 因为 我们并没有处理对旧节点进行删除的操作
多出来的旧子节点就还没有得到处理
然后 我们将 updateChildren.js 新增的判断
if (newStartIdx <= newEndIdx) {
下面加一个else if
参考代码如下
//判断旧前还是小于或等于旧后 说明旧节点有多余部分
}else if(oldStartIdx <= oldEndIdx) {
//定义一个i 接受旧前的值 一直循环到他比旧后大位置 即处理旧前到旧后之间多出来的节点
for (let i = oldStartIdx; i <= oldEndIdx; i++){
//将多余部分从dom中删除
parentElm.removeChild(oldch[i].elm);
}
}
这样 我们继续去判断 旧前和旧后 如果旧前没有大于旧后 说明还有节点没处理 即 旧节点有多余的节点
我们直接循环调用removeChild给他们都干掉
这样 我们再运行代码
点击更改dom
可以看到 这里这个d就被干掉了
但是目前就还有一个问题
例如 我们将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:"a"}, "a"),
h("p", {key:"b"}, "b"),
h("p", {key:"c"}, "c"),
h("p", {key:"e"}, "e")
]);
btn.onclick = function(){
patch( vnode, vnode1)
}
这样运行 你会发现 不但没效果 而且还死循环了
这就是因为旧后和新后没有命中到
这个问题我们后续继续解决