持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
什么是diff
在某一时间节点调用React的render方法,会创建一棵由React元素组成的树,在下一次state或props更新时,相同的render方法会返回另一棵的树。
React需要基于这两棵树之间的差别来判断如何有效地更新UI以保证当前UI与更新的树保持同步。
这个问题有一些通用的解决方案,即生成一棵树转换成另一棵树的最小操作数,然而,即使在最前沿的算法中,该算法的复杂程度O(n 3),其中n是树中元素的数量。
如果在React中使用了该算法,那么展示1000个元素所需要执行的计算量将在十亿的量级范围,这个开销是在是太过高昂,于是React在以下两个假设的基础之上提出了一套O(n)的启发式算法。
- 两个不同类型的元素会产生出不同的树。
- 开发者可以通过key prop来暗示哪些子元素在不同的渲染下能保持稳定。
在实践中,我们发现以上假设在几乎所有实用的场景下都成立。
React diff 实现
当对比两棵树时,React首先比较两棵树的根节点,不同类型的根节点元素会有不同的行为。
- 比对不同类型的元素
当根节点为不同类型的元素时,React会卸载老树并创建新树。
比如,从<div>变成<a></a>,从<Article>变成<Comment>,或者从<Button>变成<div>,这些都会触发一个完整的重建流程。
卸载老树的时候,老的DOM节点也会被销毁,组件实例会执行componentWillUnmount,创建新树的时候也会有新的DOM节点插入DOM,这个组件实例会执行componentWillMount()和componentDidMount(),当然,老树相关的state也被消除。 - 对比同类型的DOM元素
当对比同类型的DOM元素的时候,React会比对新旧元素的属性,同事保留老的,只去更新改变的属性,处理完DOM节点之后,React会递归遍历新老树。 - 比对同类型的组件元素
这个时候,React更新该组件实例的props,调用componentWillReceiveProps()和componentWillUpdate(),下一步,render被调用,diff算法递归遍历新老树 。 - 对子节点进行递归
当递归DOM节点的子元素时,React会同事遍历两个子元素的列表。
React diff的优缺点
React由于设定的diff前提,达到了O(n)的算法复杂度,在目前所有diff中属于优秀级别,再加上fiber的架构,做到了增量渲染,防止掉帧,这绝对是前端中的一大革命,思想也值得我们学习。
但是也有一些目前没有解决的缺点,比如:
- 由于React的fiber的单链表结构性质,导致React diff 查找的时候不能从两端开始查找,这在时间复杂度上是非常值得优化的一个点,React目前也在寻求优化。
- 对于React学习者来说,时间较长,其实属于缺点也属于缺优点。
React diff与Vue diff的对比
React本身架构与Vue不同,Vue中没有Fiber,目前也用不到fiber,而且React diff算法实现是基于自身架构的,React实现不了双向查找,Vue可以。