React Reconciler Diff Example
- before: abcd
- after: acdb
===第一轮遍历开始===
a(之后)vs a(之前)
key 不变,可复用
此时 a 对应的 oldFiber(之前的 a)在之前的数组(abcd)中索引为 0
所以 lastPlacedIndex = 0;
继续第一轮遍历…
c(之后)vs b(之前)
key 改变,不能复用,跳出第一轮遍历
此时 lastPlacedIndex === 0;
===第一轮遍历结束===
===第二轮遍历开始===newChildren === cdb,没用完,不需要执行删除旧节点oldFiber === bcd,没用完,不需要执行插入新节点
将剩余 oldFiber(bcd)保存为 map
// 当前 oldFiber:bcd
// 当前 newChildren:cdb
继续遍历剩余 newChildren
key === c 在 oldFiber 中存在const oldIndex = c(之前).index;
此时 oldIndex === 2; // 之前节点为 abcd,所以 c.index === 2
比较 oldIndex 与 lastPlacedIndex;
如果 oldIndex >= lastPlacedIndex 代表该可复用节点不需要移动
并将 lastPlacedIndex = oldIndex;
如果 oldIndex < lastplacedIndex 该可复用节点之前插入的位置索引小于这次更新需要插入的位置索引,代表该节点需要向右移动
在例子中,oldIndex 2 > lastPlacedIndex 0,
则 lastPlacedIndex = 2;
c 节点位置不变
继续遍历剩余 newChildren
// 当前 oldFiber:bd
// 当前 newChildren:db
key === d 在 oldFiber 中存在const oldIndex = d(之前).index;oldIndex 3 > lastPlacedIndex 2 // 之前节点为 abcd,所以 d.index === 3
则 lastPlacedIndex = 3;
d 节点位置不变
继续遍历剩余 newChildren
// 当前 oldFiber:b
// 当前 newChildren:b
key === b 在 oldFiber 中存在const oldIndex = b(之前).index;oldIndex 1 < lastPlacedIndex 3 // 之前节点为 abcd,所以 b.index === 1
则 b 节点需要向右移动
===第二轮遍历结束===
最终 acd 3 个节点都没有移动,b 节点被标记为移动
useMemo
useMemo
useMemo 是拿来保持一个对象引用不变的。useMemo 和 useCallback 都是 React 提供来做性能优化的。比起 classes, Hooks 给了开发者更高的灵活度和自由,但是对开发者要求也更高了,因为 Hooks 使用不恰当很容易导致性能问题。
假设有个 component,在 dataConfig 变化的时候重新去 fetchData:
1 | <Child |
如果是个 Class Component,会这么写:
1 | class Child extends React.Component<Props> { |
使用 Hooks 后长这样:
1 | const Child = ({ fetchData, dataConfig }: Props) => { |
使用 Class Component 时我们需要手动管理依赖,但是使用 Hooks 时会带来副作用:React 使用的是Object.is(),如果fetchData的 reference 变了,也会触发 useEffect。
虽然逻辑上 React 的处理是合理的,但是还是需要手动去解决它导致的性能问题:官方提供了 useCallback 这个 hooks,用于解决函数引用问题。