【vue设计与实现】双端Diff算法 3-添加新元素 & 移除元素

在前面的章节,非理想情况的处理,即在一轮比较过程中,不会命中四个步骤的任何一步。这时,拿新的一组子节点中的头部节点去旧的子节点中寻找可复用的节点,并非总是能找得到。
例如下面的例子:
旧子节点: p1,p2,p3
新子节点:p4,p1,p3,p2

首先进行第一轮比较,发现在四个步骤的比较中都找不到可复用的节点。于是用新子节点的头部节点p4去旧子节点中寻找相同key值的节点,发现在旧的子节点中根本就没有p4节点。这说明p4是个新增节点,那么新增节点p4应该挂载到哪里,其实因为p4是新的一组子节点中的头部节点,所以只需要将其挂载到当前头部节点之前即可。“当前”头部节点指的是,旧的一组子节点中的头部节点所对应的很是DOM节点p1
下面是完成挂载操作的代码:

while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
	// 增加两个判断分支,如果头尾部节点为undefined,则说明该节点已经被处理过了,直接跳到下一个位置
	if(!oldStartVNode){
		oldStartVNode = oldChildren[++oldStartIdx]
	}else if(!oldEndVNode){
		oldEndVNode = newChildren[--oldEndIdx]
	}else if(oldStartVNode.key === newStartVNode.key){
		// 省略部分代码
	}else if(oldEndVNode.key === newEndVNode.key){
		// 省略部分代码
	}else if(oldStartVNode.key === newEndVNode.key){
		// 省略部分代码
	}else if(oldEndVNode.key === newStartVNode.key){
		// 省略部分代码
	}else{
		const idxInOld = oldChildren.findIndex(
			node => node.key === newStartVNode.key
		)
		if(idxInOld > 0){
			const vnodeToMove = oldChildren[idxInOld]
			patch(vnodeToMove, newStartVNode, container)
			insert(vnodeToMove.el, container, oldStartVNode.el)
			oldChildren[idxInOld] = undefined
		}else{
			// 将newStartVNode作为新节点挂载到头部,使用当前头部节点oldStartVNode.el作为锚点
			patch(null,newStartVNode, container, oldStartVNode.el)
		}
		newStartVNode = newChildren[++newStartIdx]
	}

}

当条件idxInOld>0不成立时,说明newStartVNode节点是全新的节点,由于newStartVNode节点是头部节点,所以在调用patch函数挂载节点时,使用oldStartVNode.el作为锚点。
当新节点p4挂载完成后,会进行后续的更新,知道全部更新完成。
但是这样就完美了,并不是,看下面的例子:
旧子节点: p1,p2,p3
新子节点:p4,p1,p2,p3

在这里插入图片描述

在上面的例子中,按照双端Diff算法来执行更新,会发现在第二步:比较尾节点的时候,发现p3和p3可以复用,再进行下一轮更新会发现两组的尾节点p2又是可以复用的,再次更新后又会发现两组的尾节点p1又是可以复用的。
在这一轮更新完毕后,由于变量oldStartIdx的值大于oldEndIdx的值,满足更新停止的条件。这时候p4在整个更新过程中被遗漏了,没有得到任何处理,这样说明算法中存在缺陷。为了弥补这个缺陷,需要添加额外的处理代码,如下所示:

while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
	// 省略部分代码
}

// 循环结束后检查索引值的情况
if(oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx){
	// 如果满足条件,则说明有新的节点遗留,需要挂载它们
	for(let i=newStartIdx; i<=newEndIdx;i++){
		patch(null,newChildren[i],container, oldStartVNode.el)
	}
}

这样在while循环结束后增加一个if条件语句,检查四个索引值的情况。如果条件oldEndIdx<oldStartIdx && newStartIdx <= newEndIdx成立,说明新的一组子节点中有遗留的节点需要作为新节点挂载。而索引值位于newStartIdx和newEndIdx这个区间内的节点都是新节点。于是通过for循环来遍历这个区间的节点并主意挂载。挂载时的锚点仍然使用当前的头部节点oldStartVNode.el,这样就完成了对新增元素的处理。

移除不存在的元素

在解决了新增节点的问题后,再来谈论关于移除元素的问题
看这个例子,
旧子节点: p1,p2,p3
新子节点:p1,p3

可以看到,在新的子节点中p2节点已经不存在。首先按照双端Diff算法的思路执行更新
第一步旧发现头部节点p1,两者的key值相同,可以复用。

在这里插入图片描述

执行下一轮更新,会发现在第二步的时候发现尾部节点p3,两者的key值相同,可以复用。在完成此轮更新后,此时变量newStartIdx的值大于变量newEndIdx的值,满足更新停止的条件,于是更新结束。
可是这时旧子节点中存在未被处理的节点,应该将其移除。因此,需要增加额外的代码来处理,如下所示:

while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
	// 省略部分代码
}

if(oldEndIdx < oldStartIdx && oldStartIdx <= oldEndIdx){
	// 添加新节点的代码 此处省略
}else if(newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx){
	// 移除操作
	for(let i=oldStartIdx; i<= oldEndIdx; i++){
		unmount(oldChildren[i])
	}
}

与处理新增节点类似,要注意的是索引值位于oldStartIdx和oldEndIdx这个区间内的节点都应该被卸载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值