网上有太多的文章都在说React Diff 但是他们都是讲了基本的原理,但是真正使用的时候真的是这样么?
根据大佬们的源码解析,有以下几种场景
- 一次循环比对所有的节点,全部都能复用,这是非常理想的情况
- 第一循环完之后,老fiber/新fiber 其中有一个遍历完了,如果老fiber还剩下节点,那么标记删除,如果新fiber还剩下节点,标记新增
- 第一次循环中途停下来了,这之后使用了
索引对比
,就是把剩下的oldFiber
节点通过[key]: fiber
的方式存在一个map中,记录第一次遍历时候的最后一个索引为lastIndex
, 然后开始遍历newFiber.sibling
,拿遍历当前节点的key去map中找老节点有没有,如果没有就标记新增,进行下一个节点比对,如果有,判断老fiber中的索引,跟当前索引值哪个大,如果老fiber大,就按兵不动,进行下一步对比,如果老fiber节点索引比较小,那么节点复用的同时,把节点放在lastIndex后面,这样以此类推进行处理,当然这样处理完也有可能回到第二步,处理2个链表剩余的节点
这里有比较严重的一个问题,几乎没有人回答,就是这个map怎么生成的,因为不是所有的节点都有key值
,有key和无key的节点穿插在里面,这个算法还真的是这样么?几乎所有的文章都默认了节点上是有key的,也有答主说会以老fiber的index作为key,我表示怀疑
下面就用案例一起看看吧
基础代码
import React, { useEffect, useState } from 'react';
function A() {
useEffect(() => {
console.log('[create]:a');
return () => {
console.log('[unmount]: a')
}
}, [])
return 'a';
}
function B() {
useEffect(() => {
console.log('[create]:b')
return () => {
console.log('[unmount]: b')
}
}, [])
return 'b'
}
export default function DiffComponent() {
const [changeDiff, setChangeDiff] = useState(0);
console.log('[changeDiff]:', changeDiff);
return <>
<div>{changeDiff ?'diff之前' : 'diff之后'}<button onClick={() => setChangeDiff(changeDiff+1)}>diff</button></div>
<div>
<A />
<A />
<A />
<B />
<B />
<B />
</div>
</>
}
无key的时候(按照顺序比对)
常规情况
<div>
<A />
<A />
<A />
<B />
<B />
<B />
</div>
常规情况不管diff怎么点,组件都不会打印,说明复用了
长度相同,但是会有增减
<div>
{
changeDiff == 0
? (
<>
<A />
<A />
<B />
</>
): (
<>
<A />
<B />
<B />
</>
)
}
</div>
长短不一的问题
<div>
{
changeDiff == 0
? (
<>
<A />
<A />
<A />
</>
): (
<>
<A />
<A />
<B />
<B />
<B />
</>
)
}
</div>
A和B组件总量相同,前后顺序不同
<div>
{
changeDiff == 0
? (
<>
<A />
<A />
<B />
<B />
</>
): (
<>
<B />
<B />
<A />
<A />
</>
)
}
</div>
有key的时候,符合绝大数文章的diff算法
总量相同,key相同,顺序不同
<div>
{
changeDiff == 0
? (
<>
<A key="1" />
<A key="2" />
<B key="3" />
<B key="4" />
</>
): (
<>
<B key="4" />
<B key="3" />
<A key="2" />
<A key="1" />
</>
)
}
</div>
组件全部复用,基于上一个案例,有没有key逻辑是不一样的,所以多的案例就不写了,符合大家的预期
混合key进行Diff,建议先自己思考答案
<div>
{
changeDiff == 0
? (
<>
<A />
<A key="2" />
<B key="3" />
<B key="4" />
</>
): (
<>
<B key="4" />
<B key="3" />
<A key="2" />
<A />
</>
)
}
</div>
这个A到底会不会重新重建?
有文章说老fiber会用索引为key来创建map真的是这样么?
<div>
{
changeDiff == 0
? (
<>
<A />
<A key="2" />
<B key="3" />
<B key="4" />
</>
): (
<>
<B key="4" />
<B key="3" />
<A key={0} />
<A key="2" />
</>
)
}
</div>
事实证明还是重新创建了
混合key的时候,什么情况下复用
<div>
{
changeDiff == 0
? (
<>
<A key="2" />
<B key="3" />
<A />
<B key="4" />
</>
): (
<>
<B key="4" />
<B key="3" />
<A />
<A key="2" />
</>
)
}
</div>
只有相同的索引下才会复用
所以,在diff的过程中应该加上,如果没key的话,会去老fiber里面找相同位置的fiber进行比对,判断是否需要复用,如果有key,那么去map里面捞,比对类型和索引
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END