react 状态更新
# 状态更新流程概览
触发状态更新
- ReactDOM.render
- this.setState
- this.forceUpdate
- useState
- useReducer
创建update 对象
从fiber 到root(markUpdateLaneFromFiberToRoot)
调度更新(ensureRootIsScheduled)
render阶段 (入口:performSyncWorkOnRoot 或preformConcurrentWorkOnRoot)
commit阶段(入口:commitRoot)
Update: 每次状态更新创建的一个保存更新状态相关内容的对象。在render阶段的beginWork中会根据Update计算新的state。
# 更新调度
ensureRootIsSchedule
if (newCallbackPriority === SyncLanePriority) {
// 任务已经过期,需要同步执行render阶段
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root)
);
} else {
// 根据任务优先级异步执行render阶段
var schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority
);
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
}
# update 分类
ReactDOM.render/ ReactDOM.client.createRoot HostRoot
this.setState ClassComponent
this.forceUpdate ClassComponent
useState FunctionComponent
seReducer FunctionComponent
三种类型的组件可以触发更新
由于不同组件工作方式不一致,所以存在两种不同结构的update, ClassComponent| HostComponent 共用一套 Update 结构,FunctionComponent 单独一种结构
# Update 结构
ClassComponent 与HostRoot (rootFiber.tag对应类型)共用同一套Update结构
源码如下:
function createUpdate(eventTime, lane) {
var update = {
eventTime: eventTime,
lane: lane,
tag: UpdateState,
payload: null,
callback: null,
next: null
};
return update;
}
- eventTime: 任务时间,通过 performance.now() 获取的毫秒数
- lane:优先级相关字段,不同Update 优先级可能是不同的
- tag: 更新类型。UpdateState | ReplaceState | ForceUpdate | CaptureUpdate 。对于ClassComponent,payload为this.setState的第一个传参。对于HostRoot,payload为ReactDOM.render的第一个传参
- payload:更新挂载的数据,不同类型组件挂载的数据不一样,对于ClassComponent。 payload为this.setState的第一个传参。对于HostRoot, payload为ReactDOM.render的第一个传参。
- callback: 更新的回调函数,setState的回调函数,HostRoot的第三个参数回调函数
- next: 与其他update形成链表
# update与fiber的关系
Fiber节点 组成Fiber树,页面上最多存在两颗树
- 代表当前状态的ccurrent Fiber树
- 代表正在render阶段的workInProgress Fiber树
类似Fiber 节点组成 Fiber树,Fiber节点上的多个Update 会形成链表并被包含在fiber.updateQueue中。
一个fiber节点存在多个Update
在一个ClassComponent中触发this.onClick方法,方法内部调用了两次this.setState。这会在该fiber中产生两个Update。
Fiber 节点最多同时存在两个updateQueue
- current Fiber 保存的updateQueue 即 current updateQueue
- workInProgress Fiber 保存的updateQueue 即 workInProgress updateQueue
在commit阶段页面完成渲染,workInProgress Fiber树 变成 current Fiber 树,workInProgress Fiber树的Fiber 节点 对应的updateQueue 变为current updateQueue。
# updateQueue
updateQueue 数据结构:
function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}
- baseState: 本次更新前该Fiber节点的state,update基于改该state 计算更新后的state (类比于新智模型中的master分支)
- firstBaseUpdate| lastBaseUpdate:本次更新前该Fiber 节点已经保存的Update。 以链表形式存在,链表头为firstBaseUpdate,链表尾:lastBaseUpdate。之所以在更新产生前已经保存该Fiber节点内的Update,是由于有些Update 优先级低,在render阶段由update计算state时被跳过。(类比于心智模型中执行git rebase 基于的commit(节点D:紧急bug修复产生的))
- shared.pending: 触发更新时,产生的update会保存在shared.pending中形成单向环状链表。当由Update计算state时这个环会被剪开并连接在lastBaseUpdate后面。(类比心智模型中本次需要提交的commit(节点ABC)(正常迭代需求任务))
- effects:数组。保存update.callback !== null的Update
# 例子:
假设有一个fiber刚经历commit阶段完成渲染。该fiber上有两个由于优先级过低所以在上次的render阶段并没有处理的Update。他们会成为下次更新的baseUpdate。假设为u1和u2,其中u1.next === u2
fiber.updateQueue.firstBaseUpdate = u1
fiber.updateQueue.laseBaseUpdate = u2
u1.next = u2
fiber.updateQueue.baseState: u1 => u2
在fiber上触发两次状态更新,这会先后产生两个新的Update,为u3和u4。
每个 update 都会通过 enqueueUpdate 方法插入到 updateQueue 队列上
当插入u3后:
fiber.updatQueue.shared.pending === u3
u3.next === u3;
fiber.updateQueue.shared.pending表示为
u3 ─────┐
^ |
└──────┘
当u4插入:
fiber.updateQueue.shared.pending === u4;
u4.next === u3;
u3.next === u4;
fiber.updateQueue.shared.pending表示为
u4 ─> u3┐
^ |
└──────┘
enqueueUpdate 源码如下:
function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
const updateQueue = fiber.updateQueue;
// 仅在fiber已卸载时发生
if (updateQueue === null) {
return;
}
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
const pending = sharedQueue.pending;
if (pending === null) {
// 这是第一次更新。 创建一个循环列表。
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
更新调度完成后进入render阶段。shared.pending的环被打开并连接在updateQueue.lastBaseUpdate后面:
fiber.updateQueue.baseUpdate: u1 => u2 => u3 => u4
然后遍历updateQueue.baseUpdate链表,以fiber.updateQueue.baseState为初始state,依次与遍历到的每个Update计算并产生新的state(该操作类比Array.prototype.reduce)。
在遍历时如果有优先级低的Update会被跳过。
当遍历完成后获得的state,就是该Fiber节点在本次更新的state(memoizedState)。
function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderLanes: Lanes,
): void {
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
hasForceUpdate = false;
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
// 检查是否有待处理的更新。 如果有,将它们转移到baseQueue.。
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
queue.shared.pending = null;
// 待处理队列是循环的。 断开第一个之间的指针,
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
// 将 pending updates追加加到base queue
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
const current = workInProgress.alternate;
if (current !== null) {
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}
if (firstBaseUpdate !== null) {
// 遍历更新列表以计算结果。
let newState = queue.baseState;
let newLanes = NoLanes;
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
let update = firstBaseUpdate;
do {
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不足,跳过这个更新,如果他是首次跳出更新,前一个update/state作为新的基础的update/state.
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// 更新队列中剩余的优先级。
newLanes = mergeLanes(newLanes, updateLane);
} else {
// 此更新具有足够的优先级。
if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
eventTime: updateEventTime,
lane: NoLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
markRenderEventTimeAndConfig(updateEventTime, update.suspenseConfig);
// 处理此更新。
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
const callback = update.callback;
if (callback !== null) {
workInProgress.effectTag |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next;
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
}
}
state的变化在render阶段产生与上次更新不同的JSX对象,通过Diff算法产生effectTag,在commit阶段渲染在页面上。渲染完成后workInProgress Fiber树变为current Fiber树,整个更新流程到此结束。
# 优先级
runWithPriority方法接收一个优先级常量与一个回调函数作为参数。回调函数会以优先级高低为顺序排列在一个定时器中并在合适的时间触发。
function runWithPriority(priority, fn) {
var previousPriority = currentUpdatePriority;
try {
currentUpdatePriority = priority;
return fn();
} finally {
currentUpdatePriority = previousPriority;
}
}
在这个例子中,有两个Update。我们将“关闭黑夜模式”产生的Update称为u1,输入字母“I”产生的Update称为u2。 其中u1先触发并进入render阶段。其优先级较低,执行时间较长。此时:
fiber.updateQueue = {
baseState: {
blackTheme: true,
text: 'H'
},
firstBaseUpdate: null,
lastBaseUpdate: null
shared: {
pending: u1
},
effects: null
};
在u1完成render阶段前用户通过键盘输入字母“I”,产生了u2。u2属于受控的用户输入,优先级高于u1,于是中断u1产生的render阶段。
此时:
fiber.updateQueue.shared.pending === u2 ----> u1
^ |
|________|
// 即
u2.next === u1;
u1.next === u2;
其中u2优先级高于u1。
接下来进入u2产生的render阶段。
在processUpdateQueue 中, share.pending会被断开,并且拼接在baseUpdate只能够
update 顺序:ui -- u2 遍历baseUpdate ,处理u2(优先级高)
由于u2不是baseUpdate中的第一个update,在其之前的u1由于优先级不够被跳过。
update之间可能有依赖关系,所以被跳过的update及其后面所有update会成为下次更新的baseUpdate。(即u1 -- u2)。
最终u2完成render - commit阶段。
fiber.updateQueue = {
baseState: {
blackTheme: true,
text: 'HI'
},
firstBaseUpdate: u1,
lastBaseUpdate: u2
shared: {
pending: null
},
effects: null
};
然后commit阶段结尾会再调度一次更新,基于baseQueue 保存的firstBaseUpdate 进行更新
最后两次更新完成:
fiber.updateQueue = {
baseState: {
blackTheme: false,
text: 'HI'
},
firstBaseUpdate: null,
lastBaseUpdate: null
shared: {
pending: null
},
effects: null
};
我们看在第二次更新,u2又被执行了一次,相应的render阶段的生命周期勾子componentWillXXX会触发两次,所以标记上unsafe
# update的完整性
在render阶段,shared.pending 的环被打开,连接在updateQueue.laseBaseUpdate后面。 实际上shared.pending会被同时连接在workInProgress updateQueue.lastBaseUpdate与current updateQueue.lastBaseUpdate后面。
在render阶段被中断后重新开始,会基于current updateQueue 克隆出workInProgress updateQueue。由于current.updateQueue.laseBaseUpdate 中保存了上一次的Update,所以不会丢失。
在commit阶段完成渲染,由于workInProgress.updateQueue.laseBaseUpdate中保存了上一次的Update,所以workInProgress Fiber树会成current Fiber树后也不会造成Update丢失。
const current = workInProgress.alternate;
if (current !== null) {
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
# 如何保证状态依赖的连续性
当某个Update由于优先级被跳过,保存在baseUpdate中不仅是该Update,还包括链表中该Update中后续的所有Update。目的是为了保证状态的连续性。
比如:
baseUpdate:
shared.penging: A1 --> B2 --> C1 --> D2
ABCD 表示要插入页面的元素,数字表示优先级,越小优先级越高
第一次render, 优先级为1
baseState: '',
baseUpdate: null
render阶段使用的Update:【A1,C1】
memoizedState: AC
第二次render, 优先级为2
baseState: A,
baseUpdate: B2-->C1-->D2
render阶段使用的update:【B2,C1, D2】
memoizedState: 'ABCD'
为什么baseState不是第一次的memoizedState,是因为B2被跳过了,当Update被跳过时:下次更新的baseState !== 上次的memoizedState
// 优先级不足,跳过这个更新,如果他是首次跳出更新,前一个update/state作为新的基础的update/state.
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
# ReactDOM.render
renderDOM.render 会渲染FiberRootNode 和 rootFiber。其中fiberRootNode 是整个应用的根节点,rootFiber是要渲染组件所在树的根节点。
function legacyRenderSubtreeIntoContainer {
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container, /// 指ReactDOM.render的第二个参数(即应用挂载的DOM节点)
forceHydrate,
);
fiberRoot = root;
}
}
legacyCreateRootFromDOMContainer 方法会调用createFiberRoot方法完成FiberNodeRoot和rootFiber创建以及其关联,并初始化updateQueue
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建fiberRootNode
const root:FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 创建rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 连接rootFiber 与 fiberRootNode
root.current = uninitializedFiber;
uninitialized.stateNode = root;
// 初始化updateQueue
initializeUpdateQueue(unitializedFiber);
return root;
}
stateNode
rootFiber --------> FiberNodeRoot
<--------
current
# 创建update
updateConContainer代码如下:
function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function
): Lane {
// ...
// 创建update
const update = createUpdate(eventTime,lane, suspenseConfig);
// update.payload为需要挂载在根节点的组件
update.payload = {element};
// callback为ReactDOM.render的第三个参数 - 回调函数
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
// 将生成的update加入updateQueue
enqueueUpdate(current, update);
// 调度更新
scheduleUpdateOnFiber(current, lane, eventTime);
//...
}
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
// 检查是否有无限更新
checkForNestedUpdates();
...
// 向上收集fiber.childLanes
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
...
// 在root上标记更新,将update的lane放到root.pendingLanes
markRootUpdated(root, lane, eventTime);
...
// 根据Scheduler的优先级获取到对应的React优先级
const priorityLevel = getCurrentPriorityLevel();
if (lane === SyncLane) {
// 本次更新是同步的,例如传统的同步渲染模式
if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// 如果是本次更新是同步的,并且当前还未渲染,意味着主线程空闲,并没有React的更新任务在执行,那么调用performSyncWorkOnRoot开始执行同步任务
...
performSyncWorkOnRoot(root);
} else {
// 如果是本次更新是同步的,不过当前有React更新任务正在进行,而且因为无法打断,所以调用ensureRootIsScheduled,目的是去复用已经在更新的任务,让这个已有的任务把这次更新顺便做了
ensureRootIsScheduled(root, eventTime);
...
}
} else {
...
// 如果是更新是异步的,调用ensureRootIsScheduled去进入异步调度
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
}
...
}
ensureRootIsScheduled 实现:
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// 获取旧任务
const existingCallbackNode = root.callbackNode;
// 记录任务的过期时间,检查是否有过期任务,有则立即将它放到root.expiredLanes,
// 便于接下来将这个任务以同步模式立即调度
markStarvedLanesAsExpired(root, currentTime);
// 获取renderLanes
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 获取renderLanes对应的任务优先级
const newCallbackPriority = returnNextLanesPriority();
if (nextLanes === NoLanes) {
// 如果渲染优先级为空,则不需要调度
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
root.callbackNode = null;
root.callbackPriority = NoLanePriority;
}
return;
}
// 如果存在旧任务,那么看一下能否复用
if (existingCallbackNode !== null) {
// 获取旧任务的优先级
const existingCallbackPriority = root.callbackPriority;
// 如果新旧任务的优先级相同,则无需调度
if (existingCallbackPriority === newCallbackPriority) {
return;
}
// 代码执行到这里说明新任务的优先级高于旧任务的优先级
// 取消掉旧任务,实现高优先级任务插队
cancelCallback(existingCallbackNode);
}
// 调度一个新任务
let newCallbackNode;
if (newCallbackPriority === SyncLanePriority) {
// 若新任务的优先级为同步优先级,则同步调度,传统的同步渲染和过期任务会走这里
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
} else if (newCallbackPriority === SyncBatchedLanePriority) {
// 同步模式到concurrent模式的过渡模式:blocking模式会走这里
newCallbackNode = scheduleCallback(
ImmediateSchedulerPriority,
performSyncWorkOnRoot.bind(null, root),
);
} else {
// concurrent模式的渲染会走这里
// 根据任务优先级获取Scheduler的调度优先级
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority,
);
// 计算出调度优先级之后,开始让Scheduler调度React的更新任务
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
// 更新root上的任务优先级和任务,以便下次发起调度时候可以获取到
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
# ReactDOM.render流程
# this.setState
Component类型组件使用setState 在内部会调用 enqueueSetState
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
enqueueSetState 内部调用逻辑:
enqueueSetState: function (inst, payload, callback) {
// 通过组件实例获取对应fiber
var fiber = get(inst);
var eventTime = requestEventTime();
// 获取优先级
var lane = requestUpdateLane(fiber);
// 创建update
var update = createUpdate(eventTime, lane);
// payload为:this.setState的第一个传参
update.payload = payload;
// 赋值回调函数
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
// 将update 插入到updateQueue中
enqueueUpdate(fiber, update);
// 更新调度
var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}
{
markStateUpdateScheduled(fiber, lane);
}
}
# this.forceUpdate
与setState不同的是:会添加tag:ForceUpdate
enqueueForceUpdate: function (inst, callback) {
var fiber = get(inst);
var eventTime = requestEventTime();
var lane = requestUpdateLane(fiber);
var update = createUpdate(eventTime, lane);
// 赋值tag为ForceUpdate
update.tag = ForceUpdate;
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback(callback, 'forceUpdate');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}
{
markForceUpdateScheduled(fiber, lane);
}
}
然后在判断ClassComponent 是否需要更新时,根据这个tag 用来区分是否是fouceUpdate
function checkHasForceUpdateAfterProcessing(): boolean {
return hasForceUpdate;
}
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) {
const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === 'function') {
let shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext,
);
return shouldUpdate;
}
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
- checkHasForceUpdateAfterProcessing: 用来区分是否是forceUpdate
- checkShouldComponentUpdate:内部会调用shouldComponentUpdate,以及ClassComponent为PureComponent时会浅比较state与props。
当tag为forceUpdate时,不会受shouldComponentUpdate和PureComponent影响,一定会更新。