上文中我们编写了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)
}
新节点就比老节点多一个子节点
我们这边更新子节点 也分三种情况
其实处理我们这个最后多一个节点的逻辑是会比较简单的 因为他的节点是 以新节点为基础 一个一个节点去遍历的
判断如果有 就好 没有就加上
而我们key的作用在于 如果 第一个子节点 新旧节点都是li 一个内容是 c 一个内容是 a 如果你两个节点都没有key 那么 他就会确认到 key和标签名都相同 因为 标签名都是li key都没设置 就是undefined 那么他就会判断为 是同一个节点 只是文本不一样 这样 他就会将你的 a 替换文本成c
但你如果 设置了key 他就不会乱动你的东西 而是认真判断
然后 我们就可以来写这块最复杂的代码了 当新旧节点都有子节点
我们先编写代码如下
//当新旧节点都有子节点
//遍历写节点的子元素
newVnode.children.map(news => {
//定义 isExist 判断新节点中有没有和旧节点一样的元素
let isExist = false;
//遍历旧节点子集
oldVnode.children.map(old => {
//判断 如果有一模一样的元素 什么都不用做
if(old.sel == news.sel &&old.key == news.key) {
isExist = true;
}
})
//判断 如果在旧节点中没有找到相同的节点 就输出出来
if(!isExist){
console.log(news);
}
})
这里 我们先遍历新节点的所有子节点 然后遍历旧节点的所有子节点 对每一个新节点进行判断 找找看 旧节点的子节点中有没有和当前节点相同的 如果没有 我们就输出出来
然后 我们运行项目
然后 我们点击更改dom
这里 key为d的节点被输出了
我们看到index.js
我们可以很明显的看到 确实是只有 d 在旧节点中找不到相同的元素
然后 我们可以进而将代码改写成这样
//当新旧节点都有子节点
//定义un用于记录当前更新的元素下标
let un = 0;
//遍历写节点的子元素
newVnode.children.map((news,index) => {
//定义 isExist 判断新节点中有没有和旧节点一样的元素
let isExist = false;
//遍历旧节点子集
oldVnode.children.map(old => {
//判断 如果有一模一样的元素 什么都不用做
if(old.sel == news.sel &&old.key == news.key) {
isExist = true;
}
})
//判断 如果在旧节点中没有找到相同的节点 就输出出来
if(!isExist){
//通过createElement将虚拟节点变成真正的孤儿节点
let dom = createElement(news);
//当当前下标的子节点的elm属性记录上这个新创建的孤儿节点
news.elm = dom;
//判断un是不是已经大于了子节点创的 如果是 表示这个下标在老节点找不到
if(un < oldVnode.children.length) {
//在老节点对应un下标的前面插入这个节点
oldVnode.elm.insertBefore(dom, oldVnode.children[un].elm);
}else{
//直接在老节点的最后面插入
oldVnode.elm.appendChild(dom);
}
}else{
//否则就表示 本次循环中 子元素找到了相同的元素 将un+1
un++;
}
})
我们通过un记录下标 然后 我们循环遍历新节点 在新节点的变量中定义isExist 这个用来记录当前这个节点有没有相同的
然后遍历老节点 在老节点的遍历中 找有没有与新节点子元素相同
例如 我们新节点 是 a b c d 老节点是 a b c 那我 新节点遍历子节点 第一次进来的是 a 他就会循环遍历老节点的子节点 第一次 就 新节点的a和老节点下面的a是一样的
达到条件之后就会复制isExist 为ture记录
这个大家不用担心 第二次 b进循环 isExist 声明的这个代码就会创新执行 他还是初始值 false
然后 走出老节点的循环 还在新节点的循环中 还是那a节点举例 我们判断isExist
如果他不是 false 表示 新节点和老节点都有这个元素 我们就将un加一 用于记录 这个节点已经被记录了
那么 不同的只有新节点的d了
d进来 因为找不到一样 的 到最后判断isExist 就还是false
然后 我们还是通过createElement 将这个虚拟节点 变成一个真实的孤儿节点
然后 然后 news记录的就是d本身 因为 他是我们循环新节点子节点的下标 就是当前循环的元素
我们直接将新创建的孤儿节点存到当前虚拟节点的elm中
然后判断
un是不是大于旧节点的子节点数量
如果不大于 直接插入在新节点un下标的子节点的前面
例如 如果
旧节点是 a b c
新节点是 a d b c
这样 第一次 进来 a和a相同
isExist 是true un++
现在un是1
然后 第二次进来 d 找不到相同的 就会 执行到判断 un是不是大于旧节点的数量
旧节点有三个子元素 un是1 显然un小于旧节点的子集
那就执行
oldVnode.elm.insertBefore(dom, oldVnode.children[un].elm);
通过就节点的elm拿到旧节点的真实dom节点作为父级调用insertBefore 将用d虚拟节点创建的孤儿节点插入在旧节点的 un下标的节点前面 那么 旧节点的 1 下标 就是 b节点 因为下标是从零开始的
那么 旧节点此时就变成了 adbc
按我现在的路径走到 判断un时 他是大于旧节点的子集的
那么 直接就用appendChild 将他插入在 旧节点的elm的最后面
我们运行项目
点击更新dom
我们的d 就上去了
我们现在来换个顺序
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:"d"}, "d"),
h("p", {key:"b"}, "b"),
h("p", {key:"c"}, "c")
]);
btn.onclick = function(){
patch( vnode, vnode1)
}
这就是我们上面的举例
我们运行项目
点击 更改dom
也是没有任何问题
那么 问题来了 如果是交换顺序呢?
我们更改 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:"c"}, "c"),
h("p", {key:"b"}, "b")
]);
btn.onclick = function(){
patch( vnode, vnode1)
}
我们将 b和c的顺序换一下
但我们现在点击是没有任何效果的
因为 我们只考虑了元素新增 还没有处理老节点的调整
下文 我们继续来处理一下 老节点的节点变化