浅谈 React Update
1 views
在深入源码前,让我们先建立更新机制的心智模型。
1. 心智模型
1.1 同步更新的 React
我们可以将更新机制类比代码版本控制。
在没有代码版本控制前,我们在代码中逐步叠加功能。一切看起来井然有序,直到我们遇到了一个紧急线上bug(红色节点)。

为了修复这个bug,我们需要首先将之前的代码提交。
在React中,所有通过ReactDOM.render创建的应用(其他创建应用的方式参考更新状态。
即没有优先级概念,高优更新(红色节点)需要排在其他更新后面执行。
1.2 并发更新的React
当有了代码版本控制,有紧急线上bug需要修复时,我们暂存当前分支的修改,在master分支修复bug并紧急上线。

bug修复上线后通过git rebase命令和开发分支连接上。开发分支基于修复bug的版本继续开发。

在React中,通过ReactDOM.createBlockingRoot和ReactDOM.createRoot创建的应用会采用并发的方式更新状态。
高优更新(红色节点)中断正在进行中的低优更新(蓝色节点),先完成render - commit流程。
待高优更新完成后,低优更新基于高优更新的结果重新更新。
2.Update的分类
我们先来了解Update的结构。
首先,我们将可以触发更新的方法所隶属的组件分类:
- ReactDOM.render —— HostRoot
- this.setState —— ClassComponent
- this.forceUpdate —— ClassComponent
- useState —— FunctionComponent
- useReducer —— FunctionComponent
可以看到,一共三种组件(HostRoot | ClassComponent | FunctionComponent)可以触发更新。
由于不同类型组件工作方式不同,所以存在两种不同结构的Update,其中ClassComponent与HostRoot共用一套Update结构,FunctionComponent单独使用一种Update结构。
虽然他们的结构不同,但是他们工作机制与工作流程大体相同。在本节我们介绍前一种Update,FunctionComponent对应的Update在Hooks章节介绍。
3. Update的结构
ClassComponent 与 HostRoot(即rootFiber.tag对应类型)共用同一种 Update结构。
对应的结构如下:
const update: Update<*> = { eventTime, lane, suspenseConfig, tag: UpdateState, payload: null, callback: null, next: null,};你可以将Update类比心智模型中的一次commit。
Update由createUpdate方法返回,你可以从createUpdate的源码
字段意义如下:
- eventTime:任务时间,通过
performance.now()获取的毫秒数。由于该字段在未来会重构,当前我们不需要理解他。 - lane:优先级相关字段。当前还不需要掌握他,只需要知道不同
Update优先级可能是不同的。 - suspenseConfig:
Suspense相关,暂不关注。 - tag:更新的类型,包括
UpdateState|ReplaceState|ForceUpdate|CaptureUpdate。 - payload:更新挂载的数据,不同类型组件挂载的数据不同。对于
ClassComponent,payload为this.setState的第一个传参。对于HostRoot,payload为ReactDOM.render的第一个传参。 - callback:更新的回调函数。
- next:与其他
Update连接形成链表。
4. Update与Fiber的联系
我们发现,Update存在一个连接其他Update形成链表的字段next。联系React中另一种以链表形式组成的结构Fiber,他们之间有什么关联么?
答案是肯定的。
我们知道,Fiber节点组成Fiber树,页面中最多同时存在两棵Fiber树:
- 代表当前页面状态的
current Fiber树 - 代表正在
render阶段的workInProgress Fiber树
类似Fiber节点组成Fiber树,Fiber节点上的多个Update会组成链表并被包含在fiber.updateQueue中。
什么情况下一个Fiber节点会存在多个Update?
你可能疑惑为什么一个Fiber节点会存在多个Update。这其实是很常见的情况。
在这里介绍一种最简单的情况:
onClick() { this.setState({ a: 1 })
this.setState({ b: 2 })}在一个ClassComponent中触发this.onClick方法,方法内部调用了两次this.setState。这会在该fiber中产生两个Update。
Fiber节点最多同时存在两个updateQueue:
current fiber保存的updateQueue即current updateQueueworkInProgress fiber保存的updateQueue即workInProgress updateQueue
在commit阶段完成页面渲染后,workInProgress Fiber树变为current Fiber树,workInProgress Fiber树内Fiber节点的updateQueue就变成current updateQueue。
5. updateQueue
updateQueue有三种类型,其中针对HostComponent的类型我们在
剩下两种类型和Update的两种类型对应。
ClassComponent与HostRoot使用的UpdateQueue结构如下:
const queue: UpdateQueue<State> = { baseState: fiber.memoizedState, firstBaseUpdate: null, lastBaseUpdate: null, shared: { pending: null, }, effects: null,};字段说明如下:
- baseState:本次更新前该
Fiber节点的state,Update基于该state计算更新后的state。
你可以将
baseState类比心智模型中的master分支。
firstBaseUpdate与lastBaseUpdate:本次更新前该Fiber节点已保存的Update。以链表形式存在,链表头为firstBaseUpdate,链表尾为lastBaseUpdate。之所以在更新产生前该Fiber节点内就存在Update,是由于某些Update优先级较低所以在上次render阶段由Update计算state时被跳过。
你可以将
baseUpdate类比心智模型中执行git rebase基于的commit(节点D)。
shared.pending:触发更新时,产生的Update会保存在shared.pending中形成单向环状链表。当由Update计算state时这个环会被剪开并连接在lastBaseUpdate后面。
你可以将
shared.pending类比心智模型中本次需要提交的commit(节点ABC)。
- effects:数组。保存
update.callback !== null的Update。
在遍历时如果有优先级低的Update会被跳过。
当遍历完成后获得的state,就是该Fiber节点在本次更新的state(源码中叫做memoizedState)。
render阶段的Update操作由processUpdateQueue完成,你可以从这里 看到processUpdateQueue的源码
state的变化在render阶段产生与上次更新不同的JSX对象,通过Diff算法产生effectTag,在commit阶段渲染在页面上。
渲染完成后workInProgress Fiber树变为current Fiber树,整个更新流程结束。