新的子节点组多了一个节点,而旧的一组子节点没有这个节点,因将其视为新增节点。那么要更新时要将新节点正确的挂载主要分为两步:
- 要找到新增节点
- 将新增节点挂载到正确位置
下面直接看代码:
function patchChildren(n1,n2,container){
if(typeof n2.children === 'string'){
// 省略部分代码
}else if(Array.isArray(n2.children){
const oldChildren = n1.children
const newChildren = n2.children
let lastIndex = 0
for(let i=0;i<newChildren;i++){
const newVNode = newChildren[i]
let j = 0
// 在第一层循环中定义变量find,代表是否在旧的一组子节点中找到可复用的节点,
// 初始值为false,代表没找到
let find = false
for(j;j<oldChildren.length;j++){
const oldVNode = oldChildren[j]
if(newVNode.key === oldVNode.key){
// 一旦找到可复用的节点,则将变量find的值设为true
find = true
patch(oldVNode, newVNode, container)
if(j<lastIndex){
const preVNode = newChildren[i-1]
if(preVNode){
const anchor = preVNode.el.nextSibling
insert(newVNode.el,container, anchor)
}
}else{
lastIndex = j
}
break
}
}
// 如果代码运行到这里,find仍然为false
// 说明当前newVNode没有在旧的子节点组中找到可复用的节点
// 也就是说当前newVNode是新增节点,需要挂载
if(!find){
// 为了将节点挂载到正确位置,需要先获取锚点元素
// 首先获取当前newVNode的前一个vnode节点
const preVNode = newChildren[i-1]
let anchor = null
if(prevVNode){
// 如果有前一个vnode节点,则使用下一个兄弟节点作为锚点元素
anchor = prevVNode.el.nextSibling
}else{
// 如果没有前一个vnode节点,说明即将挂载的新节点是第一个子节点
// 这时我们使用容器元素的firstChild作为锚点
anchor = container.firstChild
}
// 挂载newVNode
patch(null, newVNode, container, anchor)
}
}
}else{
// 省略部分代码
}
}
这里我们目前实现的patch函数还不支持传递第四个参数,所以需要调整patch函数的代码,如下所示:
// patch函数需要接受第四个参数,即锚点元素
function patch(n1,n2,container,anchor){
// 省略部分代码
if(typeof type === 'string'){
if(!n1){
// 挂载是将锚点元素作为第三个参数传递给mountElement函数
mountElement(n2,container,anchor)
}else{
patchElement(n1,n2)
}
}else if(type === Text){
// 省略部分代码
}else if(type === Fragment){
// 省略部分代码
}
}
// mountElement 函数需要增加第三个参数,即锚点元素
function mountElement(vnode, container, anchor){
// 省略部分代码
// 在插入节点时,将锚点元素传递给insert函数
insert(el,container,anchor)
}
移除不存在的元素
在更新子节点时,不仅会遇到新增元素,还会出现元素被删除的情况,其实思路很简单,就是遍历旧的一组子节点,然后在新的一组子节点中寻找具有相同key值的节点。如果找不到就应该删除该节点,如下代码所示:
function patchChildren(n1,n2,container){
if(typeof n2.children === 'string'){
// 省略部分代码
}else if(Array.isArray(n2.children){
const oldChildren = n1.children
const newChildren = n2.children
let lastIndex = 0
for(let i=0;i<newChildren;i++){
// 省略代码
}
// 在上面更新操作完成后
// 遍历旧的一组子节点
for(let i=0;i<oldChildren.length;i++){
const oldVNode = oldChildren[i];
// 拿旧自己诶单oldVNode去新的一组子节点中寻找具有相同key值的节点
const has = newChildren.find(vnode=>vnode.key === oldVNode.key)
if(!has){
// 如果没找到,就要删除该节点
unmount(oldVNode)
}
}
}else{
// 省略部分代码
}
}