diff算法及React diff的实现、优缺点

416 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

什么是diff

在某一时间节点调用React的render方法,会创建一棵由React元素组成的树,在下一次state或props更新时,相同的render方法会返回另一棵的树
React需要基于这两棵树之间的差别来判断如何有效地更新UI以保证当前UI与更新的树保持同步
这个问题有一些通用的解决方案,即生成一棵树转换成另一棵树的最小操作数,然而,即使在最前沿的算法中,该算法的复杂程度O(n 3),其中n是树中元素的数量。
如果在React中使用了该算法,那么展示1000个元素所需要执行的计算量将在十亿的量级范围,这个开销是在是太过高昂,于是React在以下两个假设的基础之上提出了一套O(n)的启发式算法。

  1. 两个不同类型的元素会产生出不同的树。
  2. 开发者可以通过key prop来暗示哪些子元素在不同的渲染下能保持稳定
    在实践中,我们发现以上假设在几乎所有实用的场景下都成立。

React diff 实现

当对比两棵树时,React首先比较两棵树的根节点,不同类型的根节点元素会有不同的行为。

  1. 比对不同类型的元素
    当根节点为不同类型的元素时,React会卸载老树并创建新树。
    比如,从<div>变成<a></a>,从<Article>变成<Comment>,或者从<Button>变成<div>,这些都会触发一个完整的重建流程。
    卸载老树的时候,老的DOM节点也会被销毁,组件实例会执行componentWillUnmount,创建新树的时候也会有新的DOM节点插入DOM,这个组件实例会执行componentWillMount()和componentDidMount(),当然,老树相关的state也被消除。
  2. 对比同类型的DOM元素
    当对比同类型的DOM元素的时候,React会比对新旧元素的属性,同事保留老的,只去更新改变的属性,处理完DOM节点之后,React会递归遍历新老树。
  3. 比对同类型的组件元素
    这个时候,React更新该组件实例的props,调用componentWillReceiveProps()和componentWillUpdate(),下一步,render被调用,diff算法递归遍历新老树 。
  4. 对子节点进行递归
    当递归DOM节点的子元素时,React会同事遍历两个子元素的列表。

React diff的优缺点

React由于设定的diff前提,达到了O(n)的算法复杂度,在目前所有diff中属于优秀级别,再加上fiber的架构,做到了增量渲染,防止掉帧,这绝对是前端中的一大革命,思想也值得我们学习。
但是也有一些目前没有解决的缺点,比如:

  1. 由于React的fiber的单链表结构性质,导致React diff 查找的时候不能从两端开始查找,这在时间复杂度上是非常值得优化的一个点,React目前也在寻求优化。
  2. 对于React学习者来说,时间较长,其实属于缺点也属于缺优点。

React diff与Vue diff的对比

React本身架构与Vue不同,Vue中没有Fiber,目前也用不到fiber,而且React diff算法实现是基于自身架构的,React实现不了双向查找,Vue可以。