我要拿捏 react 系列三: 高性能 React (一)React 内部更新调优概述

192 阅读3分钟

来到了比较难啃的一章,所以我会放慢脚步,一节一节的更新,希望在整理的过程中,慢慢理解。

1 调和优化手段

image.png

如图,如果 D 是一个组件,并且触发了 useState 或者 setState,那么会发生什么呢? 看下代码

function markUpdateLaneFromFiberToRoot( sourceFiber, // 发生更新的fiber  
    lane // 触发了更新,产生的Lane
    ){
    /* 更新当前fiber的lanes属性*/  
    sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); let parent = sourceFiber.return;  
    /* 递归一直到 Root,更新父级 fiber 的 ChildLanes 属性 */  
    while (parent ! = = null) {

    parent.childLanes = mergeLanes(parent.childLanes, lane);

    parent = parent.return; }

}

通过以上代码我们看到,D 本身会更新 Lane 属性,接着通过 fiber 的 return 节点向上递归, 父级 A 和 B 会更新 ChildLanes 属性

1. fiber调和和组件渲染

这里会涉及一个词: 调和,什么是调和?

书中的解释是: 当从 Root 开始向下更新 fiber 状态进入 workLoop 的过程叫作调和

我的理解就是上面代码递归完成后,从root节点开始 向下 遍历 触发的更新流程

ChildLanes 又有什么作用呢?

  • 作用1: 通过该属性可以找到当事人 D,触发渲染

  • 作用2: 对于没有 ChildLanes 的分支,比如 C→F-G 就会跳过调和过程,这样就起 到了性能优化的作用,也证实了一个事实,一次更新并不是所有的 fiber 会进入调和流程中。

2. 组件类型的 fiber 调和, 但不一定渲染

在这里大家要记住的是,如果是一个组件类型的 fiber (FunctionComponent 或 ClassComponent)进入调和过程中,但是并不会触发渲染,原因是它没有发生更新

Lane 没有变化,而是它的子代 fiber发生了更新,ChildLanes 发生变化,所以它就跳过了渲染的过程,

所以 B 不会执行渲染,到了 D 组件, 因为它的 Lane 发生变化,所以会触发 Update,进而触发组件渲染, 产生了新的 Element。

接下来就到了 D 的子代元素更新流程, 因为 D 产生了新的 Element (包括 Element 上面的 props 也 是新的), 所以子代元素更新也就在所难免了。

发生了更新,就有可能更新真实的 DOM 元素, 但是 理想状态下,我们能够复用原来的 DOM, 而不会创建新的 DOM,因为 DOM 操作是十分浪费性能的, 这个时候就需要状态的复用了,

复用就需要从老的元素节点中, 找到与新的节点对应的老节点,而这 个优化的过程用到了一个技巧, 它就是 diff 算法。

2 diff 算法

问题一:React 的 diff 算法,到底是谁跟谁 diff?

问题二:diff 的范围是什么?

首先回答第一个问题,通过之前的分析,我们知道组件更新会产生新的 Element,Element 要变成 新的 fiber,所以需要从老的 fiber 中找到 与新的 Element 对应的 fiber 节点,其实是复用fiber

diff 算法是老的 fiber 和新的 Element 之间的 diff。

然后看一下第二个问题,那就是 diff 的范围,

diff 主要是针对 Children 是数组的情况,如果对于单一的元素 子节点,那么直接复用就可以了

至于diff过程和 vue diff算法的过程非常像,这里不想赘述,我会在后续 vue 原理整理的时候更详细描述

和 vue一样,diff 非常依赖 key 属性,key是判断该节点是否可复用的重要标识

所以 : React key 最好选择唯一性的 ID,如果选择 Index 作为 key,且元素发生移动,那么从移动节点开始, 接下来的 fiber 都不能得到合理的复用。