前言
setState之后发生了什么事,是一道经常会被问到的问题,在深入解读setState之前,我们先预设几个问题,希望能得到答案。
- setState的执行是同步的还是异步的?
- 多次执行setState会发生什么?
- 为什么放在setTimeout中的setState是同步的?
一些概念
-
fiber节点:React 内部的一种数据结构,是为了解决React 15版本中栈调和带来的卡顿问题。通过React Fiber这种数据结构使得React的调度算法支持可中断渲染,当优先级不够的时候,将执行权还给渲染线程,当有空闲时机的时候再继续执行调和。
-
render阶段、commit阶段:render阶段和commit阶段指的是React内部执行的两个阶段:React内部采用了双缓存的设计,有两棵树。页面上展示的是current树、当发生更新时,内存中会去构建一颗workInProgress树。render阶段的主要工作就是根据下一次的JSX对象去构建workInProgress树用于下一次渲染的展示,同时会收集effectList用于之后commit阶段的DOM操作。我们经常提起的diff算法,就是发生在这个阶段,diff算法的本质上就是尽可能复用current树中的节点去构建workInProgress树。
以下内容使用react:17.0.2、react-dom:17.0.2
版本、合成事件中的setState为例。
触发setState
代码如下,我在绑定的onClick事件中触发了setState
import React from 'react';
export default class HelloMessage extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
}
}
doSetState = () => {
this.setState((pre) => ({
number: pre.number + 1
}));
console.log('11');
}
render() {
return (
<div>
<button onClick={this.doSetState}>点我更新</button>
<span>{this.state.number}</span>
{this.state.number === 0 && <>ss11</>}
{this.state.number === 1 && <div>ss22</div>}
</div>
);
}
}
触发原型上的setState方法
我们看到setState本质上是触发了Component类原型上的setState方法。该方法会调用this.updater.enqueueSetState
Component.prototype.setState = function (partialState, callback) {
if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
{
throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." );
}
}
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
创建Update、并把Update对象入队到updateQueue
- enqueueSetState方法中首先通过
inst._reactInternals
拿当前React组件对应的React Fiber - 计算本次调度更新的优先级lane
// 主流程的代码...
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
// 当前fiber的实例
const fiber = getInstance(inst);
const eventTime = requestEventTime();
// 调度的优先级
const lane = requestUpdateLane(fiber);
// 创建update对象
const update = createUpdate(eventTime, lane);
update.payload = payload;
...
// 将创建的update对象链接到fiber上的updateQueue.shared.pending中
enqueueUpdate(fiber, update);
// 调度update
scheduleUpdateOnFiber(fiber, lane, eventTime);
},
...
};
- 通过lane和eventTime去创建一个Update对象
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
const update: Update<*> = {
eventTime,
lane,
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
return update;
}
- 将创建的update对象链接到fiber上的updateQueue.shared.pending中(是个单链表)
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
const updateQueue = fiber.updateQueue;
...
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
const pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
...
}
对于ClassComponent会在Mount的时候使用initializeUpdateQueue创建updateQueue,然后将updateQueue挂在到fiber节点上
export function initializeUpdateQueue<State>(fiber: Fiber): void { const queue: UpdateQueue<State> = { // baseState是初始化state,后面会基于这个state,和Update计算新的state baseState: fiber.memoizedState, // firstBaseUpdate、lastBaseUpdate:Update形成的链表的头和尾 firstBaseUpdate: null, lastBaseUpdate: null, // 新产生的update会以单向环状链表保存在shared.pending上,计算state的时候会剪开这个环状链表,并且链接在lastBaseUpdate后 shared: { pending: null, }, // calback不为null的update effects: null, }; fiber.updateQueue = queue; }
开始调度更新
重点来看scheduleUpdateOnFiber
这个函数。
- 首先
markUpdateLaneFromFiberToRoot
会从当前这个fiber节点向上遍历,直到拿到HostRootFiber节点, 并设置父路径上所有节点的fiber.lanes和childLanes。这两个字段可以用来辅助判断子树是否需要更新。
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
checkForNestedUpdates();
warnAboutRenderPhaseUpdatesInDEV(fiber);
// 1. 从当前的这个fiber节点向上遍历到HostRootFiber节点
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return null;
}
// Mark that the root has a pending update.
markRootUpdated(root, lane, eventTime);
...
const priorityLevel = getCurrentPriorityLevel();
if (lane === SyncLane) {
if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// .... 省略
} else {
ensureRootIsScheduled(root, eventTime); // 触发scheduler调度(调度是异步的) , 所以该函数不会立即触发render.
if (executionContext === NoContext) { // 当执行上下文为0时, 会刷新同步队列
// .... 省略部分本次讨论不会涉及的代码
flushSyncCallbackQueue();
}
}
} else {
// 2. 重头戏在这里!!!
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
...
}
}
// together more than necessary.
mostRecentlyUpdatedRoot = root;
}
- 接着在
ensureRootIsScheduled
中,scheduleCallback会以一个优先级调度render阶段
的开始函数performSyncWorkOnRoot/performConcurrentWorkOnRoot
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
...
if (newCallbackPriority === SyncLanePriority) {
// Special case: Sync React callbacks are scheduled on a special
// internal queue
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
} else if (newCallbackPriority === SyncBatchedLanePriority) {
newCallbackNode = scheduleCallback(
ImmediateSchedulerPriority,
// 在这里开始进入 render阶段
performSyncWorkOnRoot.bind(null, root),
);
} else {
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority,
);
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
// 在这里开始进入 render阶段
performConcurrentWorkOnRoot.bind(null, root),
);
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
其实从这里就可以看出来了,setState并不是同步执行的。我们本次的更新会作为一次task的回调交给任务调度器(Scheduler),等到空闲的时候才会拿出来执行。
为了验证我们的假设,可以在performSyncWorkOnRoot
上打一个断点,看看调用栈的情况,可以看到,执行render阶段,是从一个名为dispatchDiscreteEvent
函数发起的,并不是我们的setState。
进入render阶段
- 进入render阶段,主要任务就是去构建下一次需要展示的current树。主要逻辑是分别为每个节点调用
beginWork
(自顶向下)和completeWork
(自底向上)。
function performSyncWorkOnRoot(root) {
// ...
// fiber树的构建: render阶段
renderRootSync(root, lanes);
// 开启commit阶段 fiberRootNode被传递给commitRoot方法,开启commit阶段工作流程。
commitRoot(root);
return null;
}
- beginWork: 首先从rootFiber开始深度优先遍历,遍历到的每个Fiber节点,进入到beginWork。beginWork会根据不同的组件类型调用不同的处理函数
// performSyncWorkOnRoot会调用该方法
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
let next;
// 2. beginWork是向下探寻阶段
next = beginWork(current, unitOfWork, subtreeRenderLanes);
if (next === null) {
// 3. completeUnitOfWork 是回溯阶段
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;
// 尝试着复用current树中的节点
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
(__DEV__ ? workInProgress.type !== current.type : false)
) {
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
switch (workInProgress.tag) {
case HostRoot:
...
case HostComponent:
...
case ClassComponent: {
// 由于我们是类组件,
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
...
}
}
- 以类组件为例,会走到updateClassComponent。里面有一个需要重点关注的函数
processUpdateQueue
,正是处理我们updateQueue的函数,processUpdateQueue函数主要做了三件事- 构造本轮更新的
updateQueue
,并存到current节点中 - 循环遍历
updateQueue(指的是从firstBaseUpdate到lastBaseUpdate)
,计算得到newState
- 更新workInProgress节点中的
updateQueue
、memoizedState
属性
- 构造本轮更新的
export 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; //updateQueue的第一个Update
let lastBaseUpdate = queue.lastBaseUpdate; //updateQueue的最后一个Update
// 1.查看是否有pendingQueue,就是我们setState的时候存在 queue.shared.pending 中的update对象
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
// 2. 清空pending
queue.shared.pending = null;
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
// 3. 剪开环形链表
lastPendingUpdate.next = null;
// 4. 将 pendingUpdate 合并到 baseUpdate
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
// 5.在 current 树上做同样的操作
const current = workInProgress.alternate;
if (current !== null) {
// This is always non-null on a ClassComponent or HostRoot
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;
}
}
}
// These values may change as we process the queue.
if (firstBaseUpdate !== null) {
// Iterate through the list of updates to compute the result.
let newState = queue.baseState;
// TODO: Don't need to accumulate this. Instead, we can remove renderLanes
// from the original lanes.
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
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
//保存跳过的update
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
newLanes = mergeLanes(newLanes, updateLane);
} else {
//直到newLastBaseUpdate为null才不会计算,防止updateQueue没计算完
if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
eventTime: updateEventTime,
lane: NoLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
newState = getStateFromUpdate(//根据updateQueue计算state
workInProgress,
queue,
update,
newState,
props,
instance,
);
const callback = update.callback;
if (callback !== null) {
workInProgress.flags |= Callback; // 给当前Fiber打上标记,并且把callback push到effects中
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next; //下一个update
if (update === null) {//重置updateQueue
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; // 更新memoizedState
}
}
- 看一下计算newState的主要逻辑:
getStateFromUpdate
。payload就是我们调用setState传入的参数,当我们的参数为对象的话,会以Object.assign({}, prevState, partialState);
合并对象,如果是函数的话,则会将上一次Update中的state作为参数计算下一次的pritialState。
function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) {
//... 省略
case UpdateState: {
const payload = update.payload;
let partialState;
if (typeof payload === 'function') {
// ...
partialState = payload.call(instance, prevState, nextProps);
// ...
} else {
// Partial state object
partialState = payload;
}
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
return prevState;
}
// Merge the partial state and the previous state.
return Object.assign({}, prevState, partialState);
}
}
return prevState;
}
所以。从这里可以看出来,多次setState的时候,所有的payload都会被攒起来放在
updateQueue.shared
中(一个环状链表)。当需要计算的时候,会将链表剪开成单链表,然后遍历按这条链表,根据baseState计算出memoizedState。(优先级相同的情况下)
- 接着调用
reconciliChildren
,这是diff算法的核心,会尝试复用current中的fiber节点,复用不了的话会新建一个。
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
// 对于 mount 的组件
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
// 对于 update 的组件
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
- 在render阶段,除了构建workInProgress树,还需要将需要进行DOM操作的类型保存在
fiber.flags
中。至此,beginWork的工作算是做完了。
function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number,
): number {
// ... 标识这是一个新增的fiber
newFiber.flags = Placement;
return lastPlacedIndex;
}
- 接下来进入completeWork阶段。completeWork阶段也是根据不同的
fiber.tag
调用不同的处理逻辑。
如果说“递”阶段的 beginWork 方法主要是创建子节点,那么“归”阶段的
completeWork
方法则主要是创建当前节点的 DOM 节点,并对子节点的 DOM 节点和 EffectList 进行收拢。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
// ...省略
return null;
}
case HostRoot: {
// ...省略
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
// ...省略
return null;
}
// ...省略
- 来看对于
HostComponent
这个case的处理。completeWork 方法对 HostComponent 的处理主要有两个代码分支:新建或者更新。
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// 对于更新的情况,执行updateHostComonent
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
// ... 省略
// 对于新建的情况。在createInstance内部会调用 createElement 原生API
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
}
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
// 省略...
parentNamespace = ((hostContext: any): HostContextProd);
// 创建 DOM 元素
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
// 在 DOM 对象上创建指向 fiber 节点对象的属性(指针),方便后续取用
precacheFiberNode(internalInstanceHandle, domElement);
// 在 DOM 对象上创建指向 props 的属性(指针),方便后续取用
updateFiberProps(domElement, props);
return domElement;
}
- 以更新为例。
updateHostComponnet
的逻辑。主要是计算需要变化的DOM节点属性,以数组方式存储。如果updatePayload不为空,则给当前节点打上Update的EffectTag
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
type: Type,
newProps: Props,
rootContainerInstance: Container,
) {
/* 假如props没有变化(当前节点是通过bailoutOnAlreadyFinishedWork方法来复用的),可以跳过对当前节点的处理 */
const oldProps = current.memoizedProps;
if (oldProps === newProps) {
return;
}
const instance: Instance = workInProgress.stateNode;
// 省略...
/* 计算需要变化的DOM节点属性,以数组方式存储(数组偶数索引的元素为属性名,数组基数索引的元素为属性值) */
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
// 将计算出来的updatePayload挂载在workInProgress.updateQueue上,供后续commit阶段使用
workInProgress.updateQueue = (updatePayload: any);
// 如果updatePayload不为空,则给当前节点打上Update的EffectTag
if (updatePayload) {
markUpdate(workInProgress);
}
};
- 收拢EffectList。在 completeUnitOfWork 中,每个执行完 completeWork 且存在 effectTag 的 Fiber 节点会被保存在一条被称为 effectList 的单向链表中; effectList 中第一个 Fiber 节点保存在 fiber.firstEffect ,最后一个元素保存在 fiber.lastEffect。
function completeUnitOfWork(unitOfWork: Fiber): void {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
let completedWork = unitOfWork;
do {
// 在这里收拢所有的EffectList,用于
if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
(returnFiber.flags & Incomplete) === NoFlags
) {
/* 收集所有带有EffectTag的子Fiber节点,以链表(EffectList)的形式挂载在当前节点上 */
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}
/* 如果当前Fiber节点(completedWork)也有EffectTag,那么将其放在(EffectList中)子Fiber节点后面 */
const flags = completedWork.flags;
// Skip both NoWork and PerformedWork tags when creating the effect
// list. PerformedWork effect is read by React DevTools but shouldn't be
// committed.
if (flags > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
}
进入Commit阶段
-
从
performSyncWorkOnRoot
中的commitRoot
开始,就算是进入React的commit阶段了。fiberRootNode
会作为传参。在rootFiber.firstEffect
上保存了一条需要执行副作用
的Fiber节点
的单向链表effectList
,这些Fiber节点
的updateQueue
中保存了变化的props
。除此之外,一些生命周期钩子(比如
componentDidXXX
)、hook
(比如useEffect
)需要在commit
阶段执行。commit
阶段的主要工作(即Renderer
的工作流程)分为三部分:- before mutation阶段(执行
DOM
操作前) - mutation阶段(执行
DOM
操作) - layout阶段(执行
DOM
操作后)
- before mutation阶段(执行
// commit阶段的入口
commitRoot(root);
- before mutation阶段。
before mutation阶段
的代码很短,整个过程就是遍历effectList
并调用commitBeforeMutationEffects
函数处理。
// 保存之前的优先级,以同步优先级执行,执行完毕后恢复之前优先级
const previousLanePriority = getCurrentUpdateLanePriority();
setCurrentUpdateLanePriority(SyncLanePriority);
// 将当前上下文标记为CommitContext,作为commit阶段的标志
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
// 处理focus状态
focusedInstanceHandle = prepareForCommit(root.containerInfo);
shouldFireAfterActiveInstanceBlur = false;
// beforeMutation阶段的主函数
commitBeforeMutationEffects(finishedWork);
focusedInstanceHandle = null;
//整体可以分为三部分:
// 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
// 调用getSnapshotBeforeUpdate生命周期钩子。
// 调度useEffect。
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
const current = nextEffect.alternate;
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
// ...focus blur相关
}
const effectTag = nextEffect.effectTag;
// 调用getSnapshotBeforeUpdate
if ((effectTag & Snapshot) !== NoEffect) {
// 这个函数内部会调用 getSnapshotBeforeUpdate
commitBeforeMutationEffectOnFiber(current, nextEffect);
}
// 调度useEffect
if ((effectTag & Passive) !== NoEffect) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
nextEffect = nextEffect.nextEffect;
}
}
- mutaion阶段。类似
before mutation阶段
,mutation阶段
也是遍历effectList
,执行函数。这里执行的是commitMutationEffects
。
nextEffect = firstEffect;
do {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);
// commitMutationEffects会遍历effectList,对每个Fiber节点执行如下三个操作:
// 根据ContentReset effectTag重置文字节点
// 更新ref
// 根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating)
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
// 遍历effectList
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
// 根据 ContentReset effectTag重置文字节点
if (effectTag & ContentReset) {
commitResetTextContent(nextEffect);
}
// 更新ref
if (effectTag & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
}
// 根据 effectTag 分别处理
const primaryEffectTag =
effectTag & (Placement | Update | Deletion | Hydrating);
switch (primaryEffectTag) {
// 插入DOM
case Placement: {
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
break;
}
// 插入DOM 并 更新DOM
case PlacementAndUpdate: {
// 插入
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
// 更新
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// SSR
case Hydrating: {
nextEffect.effectTag &= ~Hydrating;
break;
}
// SSR
case HydratingAndUpdate: {
nextEffect.effectTag &= ~Hydrating;
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 更新DOM
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 删除DOM
case Deletion: {
commitDeletion(root, nextEffect, renderPriorityLevel);
break;
}
}
nextEffect = nextEffect.nextEffect;
}
}
- layout阶段。与前两个阶段类似,
layout阶段
也是遍历effectList
,执行函数。
root.current = finishedWork;
nextEffect = firstEffect;
do {
try {
commitLayoutEffects(root, lanes);
} catch (error) {
invariant(nextEffect !== null, "Should be working on an effect.");
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);
nextEffect = null;
// commitLayoutEffects一共做了两件事:
// 1. commitLayoutEffectOnFiber(调用`生命周期钩子`和`hook`相关操作)
// 2. commitAttachRef(赋值 ref)
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
// 调用生命周期钩子和hook
if (effectTag & (Update | Callback)) {
const current = nextEffect.alternate;
commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
}
// 赋值ref
if (effectTag & Ref) {
commitAttachRef(nextEffect);
}
nextEffect = nextEffect.nextEffect;
}
}
总结
- setState的执行是同步的还是异步的?
React中大部分的setState都是异步的,比如合成事件中触发的setState、在生命周期中触发的setState。我们本次的更新会作为一次task的回调交给任务调度器(Scheduler),等到空闲的时候才会拿出来执行。
- 多次执行setState会发生什么?
多次setState的时候,所有的payload都会被攒起来放在updateQueue.shared
中(一个环状链表)。当需要计算的时候,会将链表剪开成单链表,然后遍历按这条链表,根据baseState计算出memoizedState。内部按照Object.assign({}, prevState, partialState);
逻辑执行。
- 为什么放在setTimeout中的setState是同步的?
调试的时候可能没注意。这里补充一下。
setState是同步和异步最关键的因素是react内部的执行上下文executionContext
的状态。在ensureRootIsScheduled
这个函数中,当executionContext
为空时, 表现为同步。反之executionContext
不为空, 表现为异步。
executionContext
何时为空?当执行的setState不是通过React内部控制的时候,executionContext
则为空。比如通过addEventListener
、还有通过setTimeout/setInterval
产生的异步调用。截一张setTimeout触发setState的调用栈。
if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// .... 省略部分本次讨论不会涉及的代码
} else {
ensureRootIsScheduled(root, eventTime); // 触发scheduler调度(调度是异步的) , 所以该函数不会立即触发render.
if (executionContext === NoContext) { // 当执行上下文为0时, 会刷新同步队列
// .... 省略部分本次讨论不会涉及的代码
// 这里是关键, 执行同步回调队列. 有兴趣的同学可以继续在源码中查看, 可以得到结论:
// if分支之外的ensureRootIsScheduled(root, eventTime)和此处的flushSyncCallbackQueue()
// 最终都是调用performSyncWorkOnRoot进行fiber树的循环构建
flushSyncCallbackQueue();
}
}