本章为选读章节
是否学习该章对后续章节的学习没有影响。
在beginWork一节我们提到
对于
update的组件,他会将当前组件与该组件在上次更新时对应的Fiber节点比较(也就是俗称的Diff算法),将比较的结果生成新Fiber节点。
这一章我们讲解Diff算法的实现。
你可以从这里
(opens new window)看到
Diff算法的介绍。
为了防止概念混淆,这里再强调下
一个DOM节点在某一时刻最多会有4个节点和他相关。
current Fiber。如果该DOM节点已在页面中,current Fiber代表该DOM节点对应的Fiber节点。workInProgress Fiber。如果该DOM节点将在本次更新中渲染到页面中,workInProgress Fiber代表该DOM节点对应的Fiber节点。DOM节点本身。JSX对象。即ClassComponent的render方法的返回结果,或FunctionComponent的调用结果。JSX对象中包含描述DOM节点的信息。
Diff算法的本质是对比1和4,生成2。
Diff的瓶颈以及React如何应对
由于Diff操作本身也会带来性能损耗,React文档中提到,即使在最前沿的算法中,将前后两棵树完全比对的算法的复杂程度为 O(n 3 ),其中n是树中元素的数量。
如果在React中使用了该算法,那么展示1000个元素所需要执行的计算量将在十亿的量级范围。这个开销实在是太过高昂。
为了降低算法复杂度,React的diff会预设三个限制:
只对同级元素进行
Diff。如果一个DOM节点在前后两次更新中跨越了层级,那么React不会尝试复用他。两个不同类型的元素会产生出不同的树。如果元素由
div变为p,React会销毁div及其子孙节点,并新建p及其子孙节点。开发者可以通过
key prop来暗示哪些子元素在不同的渲染下能保持稳定。考虑如下例子:
// 更新前<div><p key="ka">ka</p><h3 key="song">song</h3></div>// 更新后<div><h3 key="song">song</h3><p key="ka">ka</p></div>
如果没有key,React会认为div的第一个子节点由p变为h3,第二个子节点由h3变为p。这符合限制2的设定,会销毁并新建。
但是当我们用key指明了节点前后对应关系后,React知道key === "ka"的p在更新后还存在,所以DOM节点可以复用,只是需要交换下顺序。
这就是React为了应对算法性能瓶颈做出的三条限制。
Diff是如何实现的
我们从Diff的入口函数reconcileChildFibers出发,该函数会根据newChild(即JSX对象)类型调用不同的处理函数。
你可以从这里
(opens new window)看到
reconcileChildFibers的源码。
// 根据newChild类型选择不同diff函数处理function reconcileChildFibers(returnFiber: Fiber,currentFirstChild: Fiber | null,newChild: any,): Fiber | null {const isObject = typeof newChild === 'object' && newChild !== null;if (isObject) {// object类型,可能是 REACT_ELEMENT_TYPE 或 REACT_PORTAL_TYPEswitch (newChild.$$typeof) {case REACT_ELEMENT_TYPE:// 调用 reconcileSingleElement 处理// // ...省略其他case}}if (typeof newChild === 'string' || typeof newChild === 'number') {// 调用 reconcileSingleTextNode 处理// ...省略}if (isArray(newChild)) {// 调用 reconcileChildrenArray 处理// ...省略}// 一些其他情况调用处理函数// ...省略// 以上都没有命中,删除节点return deleteRemainingChildren(returnFiber, currentFirstChild);}
我们可以从同级的节点数量将Diff分为两类:
当
newChild类型为object、number、string,代表同级只有一个节点当
newChild类型为Array,同级有多个节点
在接下来两节我们会分别讨论这两类节点的Diff。

(opens new window)