调和流程:beginWork 和 render 全流程

如果有一个组件 A ,如果想要它更新,那么场景有如下情况

本质上都是 state 的变化

  • 组件本身改变 state 。函数 useState | useReducer ,类组件 setState | forceUpdate

  • props 改变,由组件更新带来的子组件的更新

    • props 改变来源于父级组件的 state 变化
  • context更新,并且该组件消费了当前 context

    • context 变化来源于 Provider 中 value 变化,而 value 一般情况下也是 state 或者是 state 衍生产物

任务优先级

v17 引出的新的概念,在 v16 版本,任务的优先级用 expirationTime 表示,在 v17 版本被 lane 取缔

lane

  • 更新优先级

  • 在一次更新任务中,将赋予给更新的 fiber 的一个更新优先级 lane

childLanes

  • children 中更新优先级

  • 如果当前 fiber 的 child 中有高优先级任务,那么当前 fiber 的 childLanes 等于当前优先级

useState、setState 做了什么

image-20220331204900229
  1. 入口:创建一个任务优先级 lane
  2. scheduleUpdateOnFiber:开始更新 fiber
    • 通过当前的更新优先级 lane ,把当前 fiber 到 rootFiber 的父级链表上的所有优先级都给更新了
    • 如果当前 fiber 确定更新,那么会调用 ensureRootIsScheduled:根据任务的类型,发起异步调度任务
  3. markUpdateLaneFromFiberToRoot:标记优先级
    • 首先会更新当前 fiber 、缓冲树上的更新优先级
    • 递归向上把父级链上的 childLanes 都更新,更新成当前的任务优先级

为什么向上递归更新父级的 childLanes ?

所有的 fiber 是通过一颗 fiber 树关联到一起的,如果组件 A 发生一次更新,React 是从 root 开始深度遍历更新 fiber 树。我们不想调和整个 fiber 树,这个时候 childLanes 就派上用场了。

如果 A 发生了更新,那么先向上递归更新父级链的 childLanes,接下来从 Root Fiber 向下调和的时候,发现 childLanes 等于当前更新优先级 updateLanes,那么说明它的 child 链上有新的更新任务,则会继续向下调和,反之退出调和流程。

整个 fiber 树调和流程

image-20220331205153655

第一阶段

  • 发生更新,那么产生一个更新优先级 lane

第二阶段

  • 向上标记 childLanes 过程

第三阶段

  • 向下调和过程

为什么 A 会被调和?

原因是 A 和 B 是同级。如果父级元素调和,并且向下调和,那么父级的第一级子链上的 fiber 都会进入调和流程。从 fiber 关系上看,Root 先调和的是 child 指针上的 A ,然后 A 会退出向下调和,接下来才是 sibling B,接下来 B 会向下调和,通过 childLanes 找到当事人 F,然后 F 会触发 render 更新。

从 beginWork 到组件更新全流程

image-20220331205520092

以组件B 为参考。来看一下 React 如何调和的。那么一次更新就有可能有三种场景:

本质上都在 beginWork 中进行的

场景一:更新 A 组件 state

A 触发更新,如果 B,C 没有做渲染控制处理(比如 memo PureComponent),那么更新会波动到 B 、 C, A、B、C 都会 rerender

img

更新流程

当更新 A 时候,那么 A 组件的 fiber 会进入调和流程,会执行 render 形成新的组件 B 对应的 element 元素,接下来调和 B ,因为 B 的 newProps 不等于 oldProps,所以会 didReceiveUpdate = true ,然后更新组件,也会触发 render。(这里都是默认没有渲染控制的场景,比如 memo PureComponent 等 )

img

场景二:当更新 B 组件

组件 A fiber 会被标记,然后 A 会调和,但是不会 rerender。组件 B 是当事人,既会进入调和,也会 rerender。组件 C 受到父组件 B 的影响,会 rerender。

img

更新流程

当更新 B 时候,那么 A 组件会标记 childLanes,所以 A 会被调和,但是不会 render,然后到了主角 B ,B 由于新老 props 相等,所以会 checkScheduledUpdateOrContext 流程,判断 lane 等于 renderLanes ,检查到 lane 等于 renderLane,所以会执行更新,触发 render。 C 组件也就跟着更新。

img

场景三:当更新 C组件

A,B 会进入调和流程,但是不会 rerender,C 是当事人,会调和并 rerender

img

更新流程

更新 C 时候,那么 A 和 B 组件会标记 childLanes,所以 A 和 B 会被调和,但是不会更新,然后到 C ,C 会走正常流程。

img

场景四:什么时候 B 会跳出调和流程呢

image-20220331205636178

beginWork 流程图

image-20220331210322372

组件 A 触发 setState 或者 useState 更新视图,既然 fiber 是从 root 开始更新,那么如何找到对应的 A 并 rerender 的呢?

Root Fiber 是通过 childLanes 逐渐向下调和找到需要更新的组件的。

如果 A 发生了更新,那么先向上递归更新父级链的 childLanes,接下来从 Root Fiber 向下调和的时候,发现 childLanes 等于当前更新优先级 updateLanes,那么说明它的 child 链上有新的更新任务,则会继续向下调和,反之退出调和流程。

image-20220331205153655

组件类型 fiber 进行 beginWork 就一定会进行 render 吗?

不会。看上面的流程图,总的来说:会比较 newProps 、oldProps,结合有无 memo 等优化策略,比较任务优先级等。