React diff 原理分析

1,300 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

前言

diff 作为 Virtual DOM 的加速器,其算法上改进优化是 React 整个界面渲染的基础和性能保障。diff会帮助我们计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行原生 DOM 操作,而非重新渲染整个页面,从而办证了每次操作更新后页面的高效渲染。

diff 策略

  • 策略一:Web UIDOM 节点跨层级的移动操作特别少,可以忽略不计
  • 策略二:拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
  • 策略三:对于同一层级的一组子节点,它们可以通过唯一的 id 进行区分

基于以上策略,React分别对 tree diffcomponent diffelement diff 进行算法优化。

tree diff

React 对树进行分层比较,两棵树只会对同一层次的节点进行比较。React 通过 updateDepthVirtual DOM 树进行层级控制,只会对相同层级的 DOM 节点进行比较,即同一个父节点下的所有节点。当发现节点已经不存在时,则该节点及其子节点会被完全删除,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

如果出现跨层级的移动怎么办呢?

React 只会简单地考虑同层级节点的位置变换,而对于不同层级的节点,只有创建和删除操作。

看下图 节点 A 移动到 节点 D 之后

image.png

  1. 发现 节点A 消失,直接销毁 A
  2. D 节点发现多了一个 A 节点,则会创建 A(包括子节点)作为其子节点。 此时,diff 的执行情况:creat A -> creact B -> create C -> delete A

React 官方建议不要进行 DOM 节点跨层级的操作

component diff

  • 如果是同一类型的组件,按照原策略继续比较 Virtual DOM 树即可
  • 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切知道这点,那么就可以节省大量的 diff 运算时间。因此,React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff 分析。

看下图: image.png 当组件 D 变化为 G 时,即使两个组件结构相似,一旦 React 判断 DG 是不同类型的组件,就不会比较二者的机构,而是直接删除 D,重新创建组件 G及其子节点。

element diff

当节点处于同一个层级时,diff 提供了 3 种节点操作,分别为 INSERT_MARKUP(插入)MOVE_EXISTING(移动)REMOVE_NODE(删除)

  • INSERT_MARKUP:新的组件类型不在旧集合里,即全新的节点,需要对新节点执行插入操作。
  • MOVE_EXISTING:旧集合中有新组件类型,且 element 是可更新的类型,就需要做移动操作,可以复用以前的 DOM 节点。
  • REMOVE_NODE:旧组件类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者旧组件不在新集合里的,也需要执行删除操作。

image.png

对同一层级的同组子节点,添加唯一 key 进行区分,从而提升 diff 性能

diff 差异化对比后,通过 key 发现新旧集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将旧集合中节点的位置进行移动,更新为新集合中节点的位置,diff 结果为:BD 不做任何操作,AC进行移动操作。

结语

如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。

文章如有错误之处,希望在评论区指正🙏🙏