我们知道,React在更新视图时,会比较新旧树的节点,计算出差异,然后根据差异来进行修改。对于同一层级的子节点,如果按顺序依次比较,发现不同则删去原有的,插入新的。这样虽然可行,但如果这一层只是更换了顺序,那么频繁地删除、插入节点,效率会很低。为了提高子元素对比的性能,React使用了基于key的算法,能够更好地复用已有的节点。
这里只介绍其实现思路,不展示具体的源代码。
简化模型
假如有5个人已经排好了队,我们按顺序标为1,2,3,4,5号。现在要重新排为3,2,1,5,4,那么怎样有一个标准的方法重排呢?
React的算法分为两步,先标记出需要移动的元素,再按一定的标准移动。
标记
第一步:根据新队列的顺序对元素遍历,如果新队列中未来在它左边的元素都已经在它左边,那么它可以不移动。
我们从3开始,3未来左边没有元素,所以3不用移动,而对于新队列3之后的元素,想要不移动,它原来的位置肯定要大于3,3成为第一个定位的标准;
2 < 3,标记为要移动;
1 < 3,标记为要移动;
5 > 3,不用移动,并成为新的定位标准;
4 < 5,标记为要移动。
标记结果如图:
移动
我们确定了固定和移动的元素,那么具体要如何以固定元素为参照来移动呢?移动DOM元素的方法有insertBefore、appendChild,这里就要用到这两种方法。
第二步:同样按新队列的顺序遍历,寻找之后第一个固定的元素。如果有,就insertBefore;没有就appendChild到最后。
移动过程如图:
实际实现
React不会一开始上来就尝试重排,而是按顺序依次比较,key值匹配时(包括都为null的情况),会一直持续下去。停止的原因三种:
原来的节点不够了,这时新的多余的节点都直接插入;
新的节点不够了,这时原有多余的节点都删除;
出现key值不匹配时,开始重排。
三种情况如下:
React会将旧节点根据key或者index存入Map结构中,然后遍历新节点,根据新节点的key或index在Map中寻找旧节点。如果没找到,证明这是一个全新节点,需要插入;如果找到了,则按之前的重排算法确定是否移动,并从Map中删去旧节点。当遍历完成后,Map中剩余的则是需要删除的节点。