深入理解React渲染阶段中的Diff算法与复用策略
Howard 11/24/2023 React
React 在 render 阶段采用 Diff 算法来比较新旧虚拟 DOM 树,以实现高效的更新。本文将深入解析 React 的 Diff 算法,并重点关注其三种复用策略,帮助开发者更好地优化 React 应用性能。
React 的 Diff 算法主要采用三种策略:
- 同层级节点比较
React 只会比较同层级的节点,对于跨层级的节点会忽略不计。
上图所示,在节点 A 的更新中,React 会删除 A 及其子节点 B、C,为 D 子节点创建子节点 A,B,C - 不同类型元素的处理
当两个元素类型不同时,React 会销毁旧元素及其子孙节点,并创建新元素及其子孙节点。如果元素由 div 变为 p,React 会销毁 div 及其子孙节点,并新建 p 及其子孙节点。
虽然 D 跟 G 的子树是相似的,但是因为 D 和 G 是两个不同的节点,那么 React 就会把 D 和其子节点删除后,再重新生成 G,E,F 节点 - 使用
key
prop 进行稳定标记
通过key
prop 可以暗示哪些子元素在不同渲染下能保持稳定。对于单节点复用和多节点复用,复用策略都是先比较key
,然后再比较类型。
如果新旧都没有指定 key,那么 key 就是 null。
单节点复用
- 如果节点的
key
相同但type
不同,将该节点及其兄弟节点的 Fiber 全部标记删除。 - 如果节点的
key
不同,将该节点标记删除,并遍历其兄弟节点。
多节点复用
- 主要判断节点是新增,删除和更新操作
- React 会进行两轮遍历
a. 第一轮遍历的作用: 处理需要更新的节点
b. 第二轮遍历的作用: 处理剩下的不属于更新的节点 - 进行第一轮遍历,直到 newChildren 或者 oldFiber 遍历完,或者满足第 c 点。
a. 如果key
相同,**type
相同,进行复用,继续遍历
b. 如果key
相同,type
**不相同,该 fiber 节点标记删除,继续遍历
c. 如果key
不同,跳出遍历 - 进行第二轮遍历
a. 如果newChildren
和oldFiber
遍历完,皆大欢喜,最完美情况
b. 如果newChildren
遍历完,oldFiber
没有,oldFiber
全部标记删除
c. 如果newChildren
没有遍历完,oldFiber
遍历完,newChildren
全部标记添加
d. 如果都没有遍历完,进入核心部分 - 会以
key - fiber
的形式将oldFiber
保存在 map 中,然后遍历newChildren
,以最后一个可复用的节点为参照物,用参照物 index 和oldFiber
的 index 进行判断是否移动了。
对于 A 节点来说,以 B 为参照物,标记移动了, D,C 同理。
对于 ABC 节点来说,以 D 为参照物,标记移动了。
简单来说,会固定后面的复用节点,前面的节点就会被标记移动。
结论
为了优化性能,尽量减少将节点从后面移动到前面的操作,特别是在涉及大量节点的列表渲染中。