React v17.0.2源码
资源一览
React 内部函数调用关系草图
Mount 阶段大致如下,可供参考,缩进意为被上一层调用,标记!!!的注释意为核心代码。
ReactDOM.render;
legacyRenderSubtreeIntoContainer;
legacyCreateRootFromDOMContainer;
createRoot;
new ReactDOMRoot();
createRootImpl;
createContainer;
createFiberRoot;
new FiberRootNode();
createHostRootFiber;
createFiber;
new FiberNode();
initializeUpdateQueue;
unbatchedUpdates;
updateContainer;
requestEventTime;
requestUpdateLane;
getContextForSubtree;
createUpdate;
enqueueUpdate;
scheduleUpdateOnFiber; // !!! reconcile开始
markUpdateLaneFromFiberToRoot; // 从当前节点到根节点,依次更新节点的Lane
markRootUpdated; // 根节点标记为:存在pending update
performSyncWorkOnRoot; // !!! reconcile开始
getNextLanes;
renderRootSync;
prepareFreshStack; // 初始化Work Loop必需的所有变量
createWorkInProgress;
createFiber;
enqueueInterleavedUpdates;
pushDispatcher; // 设置一个ReactCurrentDispatcher,主要用于处理Hooks
workLoopSync; // !!! 循环处理unitOfWork,正在处理的work称为workInProgress
performUnitOfWork;
beginWork; // !!! 沿child构建Fiber,直到child为null
updateHostRoot; // !!! 不同的任务有不同的函数去处理
pushHostRootContext;
cloneUpdateQueue;
processUpdateQueue; // 处理update queue,计算出最终state
reconcileChildren; // 处理children
mountChildFibers;
reconcileChildFibers;
placeSingleChild;
reconcileSingleElement;
completeUnitOfWork; // !!! 将sibling设为workInProgress,没有则沿return找父节点的sibling,直到return为null
completeWork;
resetContextDependencies;
popDispatcher; // render结束,还原Dispatcher
commitRoot;
commitRootImpl;
commitBeforeMutationEffects; // !!! 1阶段,Before Mutation,执行生命周期函数
prepareForCommit;
commitBeforeMutationEffects_begin;
commitBeforeMutationEffects_complete;
commitBeforeMutationEffectsOnFiber; // 执行getSnapshotBeforeUpdate
commitMutationEffects; // !!! 2阶段,根据fiber.flags更新DOM
commitMutationEffects_begin;
commitMutationEffects_complete;
commitDetachRef; // 解除ref
commitAttachRef; // 重新设置ref
commitPlacement;
insertOrAppendPlacementNodeIntoContainer; // React DOM Api
insertOrAppendPlacementNode; // React DOM Api
commitWork;
commitHookEffectListUnmount;
commitContainer;
replaceContainerChildren; // React DOM Api
commitLayoutEffects; // !!! 3阶段,执行layout
commitLayoutEffects_begin; // componentDidMount
safelyCallCommitHookLayoutEffectListMount;
commitHookEffectListMount; // HookLayout
ensureRootIsScheduled;
entangleTransitions;
getPublicRootInstance;
Update 阶段(setState,其他类似)
Component.prototype.setState;
classComponentUpdater.enqueueSetState;
createUpdate;
enqueueUpdate;
scheduleUpdateOnFiber; // !!!核心函数
ensureRootIsScheduled;
performConcurrentWorkOnRoot;
renderRootConcurrent;
pushDispatcher;
workLoopConcurrent; // !!!循环处理unitOfWork,正在处理的work称为workInProgress
shouldYield; // !!!来自scheduler,为true时停止任务处理
performUnitOfWork; // 和Mount类似,只不过只处理更新阶段对应的任务,以下省略
popDispatcher;
finishConcurrentRender;
commitRoot; // 省略
Mount 基本流程
- React.createElement()构建出 React DOM;
- ReactDOM.render()是 React App 初始化的起点,以下 API 如无特殊说明,默认属于
reconciler库;- 构建两个互相引用的实例:fiberRoot 是整个 App 的根节点,rootFiber 是基于 ReactDOM 构建的 Fiber Tree 的根节点;
- updateContainer 正式开始 reconcile 过程,在此期间触发的更新以 unbatched 形式执行,主要执行两个函数:
- renderRootSync,进入 render 阶段,核心函数调用为:
- pushDispatcher,初始化一个 ReactCurrentDispatcher,内含所有 React Hook 的实现;
- workLoopSync,不同类型的 FiberNode 在 render 阶段对应不同的任务(例如类组件和函数组件,任务显然不同),WorkLoopSync 通过一个循环同步地处理这些任务;
- popDispatcher,还原 ReactCurrentDispatcher;
- commitRoot,进入 commit 阶段,根据 Diff 结果更新浏览器 DOM,触发对应 Effect Hook 或生命周期方法;
- renderRootSync,进入 render 阶段,核心函数调用为:
类组件 Update 基本流程
类组件有两种更新组件的方式:
- setState;
- forceUpdate;
这两个都是定义在 React.Component.prototype 上的方法,流程相似。
Component.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, "setState");
};
Component.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
};
以 setState 为例的流程:
- 创建 update 对象并添加进 updateQueue;
- 调用 scheduleUpdateOnFiber,开始处理更新:
- 如果是 batchedUpdates,则每次取消上个调度任务,再重新申请一个新的调度任务;
- 否则直接申请一个调度任务;
- 轮到该任务执行时,进入 workLoop
注:被调度的任务就是performConcurrentWorkOnRoot函数;
classComponentUpdater
实际调用的是this.updater.enqueueSetState,this.updater是在 render 阶段的updateClassComponent -> constructClassInstance -> adoptClassInstance函数中注入的。
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
// 注意这一行!!!instance就是ClassComponent的实例
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
// The instance needs access to the fiber so that it can schedule updates
setInstance(instance, workInProgress);
}
对照关系:
- setState:实际调用 classComponentUpdater.enqueueSetState;
- forceUpdate:实际调用 classComponentUpdater.enqueueForceUpdate;
其实这两个方法逻辑几乎一样,唯一不同点是 setState 需要携带上 payload,也就是组件中调用 setState 时传入的 partialState,而 forceUpdate 不需要携带 payload,只是把 update.tag 置为 ForceUpdate(一个二进制数)。
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
enqueueUpdate(fiber, update, lane);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}
},
enqueueForceUpdate(inst, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const update = createUpdate(eventTime, lane);
update.tag = ForceUpdate;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
enqueueUpdate(fiber, update, lane);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}
},
};
Update Queue
setState/forceUpdate/react hook 等触发的更新会创建一个 update 对象,并添加到 updateQueue,这是为了实现更新批处理而设计的,批处理生效期间,多次更新按顺序加到 updateQueue 中,期间每个更新会取消上一次更新的任务调度,并申请一次新的任务调度(scheduler 提供任务调度能力),直到批处理结束,进入下一轮事件循环,被调度的任务得到执行机会,真正进入 render 阶段并通过processUpdateQueue一次性处理所有更新。
React 合成事件默认开启更新批处理,其他场景必须手动调用unstable_batchedUpdates,例如异步的回调(setTimeout、Promise.then 等)是无法批处理的,这里引申一个常见问题——setState 是异步还是同步的?
[注 1]:batched updates 参考这篇博客——React 源码解读之 Automatic Batching
[注 2]:update queue 结构、计算过程参考这篇博客——React 源码细读-UpdateQueue 机制与 Lane 优先级模型
setState是异步还是同步的?
合成事件与生命周期函数中是异步的,异步函数回调与原生事件中是同步的。
注意,此处同步、异步并非指 setState 在 JS 引擎层面的同步或异步,而是指 this.state 的更新能否立刻体现出来:
- 同步:setState 后立刻可以通过 this.state 取到更新后的 state;
- 异步:setState 后不能立刻取到更新后的 state;
从源码层面解释:
- 合成事件默认开启更新批处理,setState 执行后并不会在本次事件循环中完成 state 更新,因此批处理结束前不能获取到最新 state;
- 生命周期方法中的 setState 执行后,state 的赋值不是立刻执行的,因此也无法得到最新 state;
异步函数、原生事件的回调中不会应用更新批处理,因此每个 setState 都会触发一次 render、commit,因此可以 setState 后立刻获得最新 state;
scheduleUpdateOnFiber
update 会被赋予一个优先级,根据优先级选择对应的任务调度方式,SyncLane 直接调用performSyncWorkOnRoot处理。
// scheduleUpdateOnFiber
if (lane === SyncLane) {
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
performSyncWorkOnRoot(root);
} else {
//...
}
} else {
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root, eventTime);
}
其他优先级通过ensureRootIsScheduled->scheduleCallback调度一个performConcurrentWorkOnRoot函数。
// ensureRootIsScheduled
// !!!batchedUpdates情况下,取消上一次调度
if (existingCallbackNode != null) {
// Cancel the existing callback. We'll schedule a new one below.
cancelCallback(existingCallbackNode);
}
// !!!申请一个新的调度任务
let newCallbackNode;
if (newCallbackPriority === SyncLane) {
// ...
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
// !!!看这里,调度一个performConcurrentWorkOnRoot
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
}
performConcurrentWorkOnRoot得到执行机会时,会去执行renderRootConcurrent,若已经超时则直接以同步优先级执行renderRootSync.
// performConcurrentWorkOnRoot
let exitStatus =
shouldTimeSlice(root, lanes) &&
(disableSchedulerTimeoutInWorkLoop || !didTimeout)
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
Work Loop
Work Loop 根据 Legacy/Concurrent 模式的不同,分为两套逻辑,但总的来说,不同 Fiber 节点的不同阶段各有不同的任务,二者都是为了循环处理每个 Fiber 节点对应的任务:
workLoopSyncworkLoopConcurrent
workLoop 都是在 try catch 块中调用的,原因就是为了在 catch 块中通过handleError处理错误,如下:
// renderRootSync
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// renderRootConcurrent
do {
try {
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
核心逻辑中的相关变量定义:
workInProgress:当前正在处理的 Fiber;unitOfWork:每个 Fiber 节点都有对应的一系列任务,译为工作单元;
Fiber 节点重要属性:
child:指向首个子节点;sibling:指向右侧首个兄弟节点(此处右指的是 ReactDOM 画图后方向上的右,注意树是没有逻辑上的左右之分的,只有二叉树有);return:指向父节点;flags:用于在 render 阶段添加一些标记如 Update、Delete、Snapshot 等,以便 render 后续逻辑或 commit 阶段通过这些标记执行特定逻辑;tag:组件类型,如 ClassComponent、FunctionComponent;
注:flags 是用二进制位表示各种标记,位运算符实现加、删、验;
异常处理机制
上文可知 handleError 就是 React 组件抛错的处理入口,该函数通过 throwException 来处理组件异常,核心逻辑:
- 沿 return 遍历父组件,找到最近的 ErrorBoundary,创建一个 ErrorUpdate,处理该 Update 时,将调用生命周期方法:
- getDerivedStateFromError
- componentDidCatch
- 如果没有找到 ErrorBoundary,则在 HostRoot 上触发一个 ErrorUpdate,该 Update 将打印一些 Error Log;
注:实现了getDerivedStateFromError或componentDidCatch的类组件称为 ErrorBoundary.
// throwException
value = createCapturedValue(value, sourceFiber);
let workInProgress = returnFiber;
do {
switch (workInProgress.tag) {
case HostRoot: {
const errorInfo = value;
workInProgress.flags |= ShouldCapture;
const lane = pickArbitraryLane(rootRenderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
const update = createRootErrorUpdate(workInProgress, errorInfo, lane);
enqueueCapturedUpdate(workInProgress, update);
return;
}
case ClassComponent:
// Capture and retry
const errorInfo = value;
const ctor = workInProgress.type;
const instance = workInProgress.stateNode;
if (
(workInProgress.flags & DidCapture) === NoFlags &&
(typeof ctor.getDerivedStateFromError === "function" ||
(instance !== null &&
typeof instance.componentDidCatch === "function" &&
!isAlreadyFailedLegacyErrorBoundary(instance)))
) {
workInProgress.flags |= ShouldCapture;
const lane = pickArbitraryLane(rootRenderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
// Schedule the error boundary to re-render using updated state
const update = createClassErrorUpdate(workInProgress, errorInfo, lane);
enqueueCapturedUpdate(workInProgress, update);
return;
}
break;
default:
break;
}
workInProgress = workInProgress.return;
} while (workInProgress !== null);
ErrorUpdate 与普通 Update 的处理类似,ErrorUpdate 可看作定制化的 Update,这里的定制指:
- tag 标记为 CaptureUpdate,意为抛出异常所触发的一次特殊更新;
- payload,将调用 getDerivedStateFromError;
- callback,将调用 componentDidCatch;
function createClassErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
lane: Lane
): Update<mixed> {
const update = createUpdate(NoTimestamp, lane);
update.tag = CaptureUpdate;
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
if (typeof getDerivedStateFromError === "function") {
const error = errorInfo.value;
update.payload = () => {
logCapturedError(fiber, errorInfo);
return getDerivedStateFromError(error);
};
}
const inst = fiber.stateNode;
if (inst !== null && typeof inst.componentDidCatch === "function") {
update.callback = function callback() {
if (__DEV__) {
// ...
}
if (typeof getDerivedStateFromError !== "function") {
// To preserve the preexisting retry behavior of error boundaries,
// we keep track of which ones already failed during this batch.
// This gets reset before we yield back to the browser.
// TODO: Warn in strict mode if getDerivedStateFromError is
// not defined.
markLegacyErrorBoundaryAsFailed(this);
// Only log here if componentDidCatch is the only error boundary method defined
logCapturedError(fiber, errorInfo);
}
const error = errorInfo.value;
const stack = errorInfo.stack;
this.componentDidCatch(error, {
componentStack: stack !== null ? stack : "",
});
if (__DEV__) {
// ...
}
};
} else if (__DEV__) {
// ...
}
return update;
}
workLoopSync
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
workLoopSync 很简单,就是循环处理 workInProgress,处理完一个,再将下一个 Fiber 节点赋值给 workInProgress,处理顺序可看作树的 DFS,或者将 FiberTree 看作孩子-兄弟表示法得到的二叉树(左孩子,右兄弟),则顺序就是二叉树的先序遍历:
伪代码表示:
// 1 如果child存在,则切换到child;
// 2 否则:
// 2.1 如果sibling存在,切换到sibling,跳到1;
// 2.2 否则,通过return切换到父节点,跳到2.1;
performUnitOfWork
performUnitOfWork 就是 unitOfWork 的处理函数,它的作用就是处理当前任务,并得到下个待处理的 Fiber 节点;
注意 FiberTree 是有两颗的,互相以 alternate 指向各自对应节点,这两颗树一个是 workInProgress,代表当前正在处理的页面;一个是 current,代表当前正在显示的页面;
function performUnitOfWork(unitOfWork: Fiber): void {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
// ...
} else {
next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
//resetCurrentDebugFiberInDEV();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
beginWork
beginWork 代表任务开始处理,根据 Mount、Update 阶段的不同,以及组件类型的不同,各有其对应的处理函数,这里主要介绍 ClassComponent.
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
// ...
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
// ...
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes
);
}
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
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
// ...
}
}
updateClassComponent
updateClassComponent 相对比较简单,核心逻辑就是:
- constructClassInstance,组件 Mount 时调用
- mountClassInstance,组件 Mount 时调用
- updateClassInstance,组件 Update 时调用
- finishClassComponent,beginWork 结束时调用,获取下一个 unitOfWork;
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes
) {
if (__DEV__) {
// ...
}
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderLanes);
const instance = workInProgress.stateNode;
let shouldUpdate;
if (instance === null) {
if (current !== null) {
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.flags |= Placement;
}
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes
);
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes
);
}
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes
);
if (__DEV__) {
// ...
}
return nextUnitOfWork;
}
constructClassInstance
核心逻辑:
- 实例化类组件
- 通过 adoptClassInstance 注入 ReactUpdater
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any
): any {
let isLegacyContextConsumer = false;
let unmaskedContext = emptyContextObject;
let context = emptyContextObject;
const contextType = ctor.contextType;
if (__DEV__) {
// ...
}
if (typeof contextType === "object" && contextType !== null) {
context = readContext((contextType: any));
} else if (!disableLegacyContext) {
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
const contextTypes = ctor.contextTypes;
isLegacyContextConsumer =
contextTypes !== null && contextTypes !== undefined;
context = isLegacyContextConsumer
? getMaskedContext(workInProgress, unmaskedContext)
: emptyContextObject;
}
// Instantiate twice to help detect side-effects.
if (__DEV__) {
// ...
}
const instance = new ctor(props, context);
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);
adoptClassInstance(workInProgress, instance);
if (__DEV__) {
// ...
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// ReactFiberContext usually updates this cache but can't for newly-created instances.
if (isLegacyContextConsumer) {
cacheContext(workInProgress, unmaskedContext, context);
}
return instance;
}
就是在 Mount 时通过该函数注入classComponentUpdater,具体机制已经介绍过。
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
// The instance needs access to the fiber so that it can schedule updates
setInstance(instance, workInProgress);
if (__DEV__) {
instance._reactInternalInstance = fakeInternalInstance;
}
}
mountClassInstance
主要逻辑就是触发生命周期函数:
- getDerivedStateFromProps()
- UNSAFE_componentWillMount()
function mountClassInstance(
workInProgress: Fiber,
ctor: any, // constructor缩写
newProps: any,
renderLanes: Lanes
): void {
// ...
const instance = workInProgress.stateNode;
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
initializeUpdateQueue(workInProgress);
const contextType = ctor.contextType;
if (typeof contextType === "object" && contextType !== null) {
instance.context = readContext(contextType);
} else if (disableLegacyContext) {
instance.context = emptyContextObject;
} else {
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
instance.context = getMaskedContext(workInProgress, unmaskedContext);
}
if (__DEV__) {
// ...
}
instance.state = workInProgress.memoizedState;
// 调用定义在组件构造函数上的static getDerivedStateFromProps()
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === "function") {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps
);
instance.state = workInProgress.memoizedState;
}
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
typeof ctor.getDerivedStateFromProps !== "function" &&
typeof instance.getSnapshotBeforeUpdate !== "function" &&
(typeof instance.UNSAFE_componentWillMount === "function" ||
typeof instance.componentWillMount === "function")
) {
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
instance.state = workInProgress.memoizedState;
}
if (typeof instance.componentDidMount === "function") {
let fiberFlags: Flags = Update;
if (enableSuspenseLayoutEffectSemantics) {
fiberFlags |= LayoutStatic;
}
// ...
workInProgress.flags |= fiberFlags;
}
}
updateClassInstance
该函数较为复杂,只有更新类组件时调用,主要作用:
- 直接调用部分生命周期方法;
- 检测部分生命周期方法,并在 fiber 上添加特定 flags,以便在 commit 阶段调用对应生命周期方法;
- processUpdateQueue 计算 updates 并得到 state;
- 根据 props、state 的全等比较,以及 shouldComponentUpdate 的调用结果,返回一个 shouldUpdate 布尔值,指示是否抛弃本次更新;
该函数将触发以下生命周期方法:
- UNSAFE_componentWillReceiveProps
- getDerivedStateFromProps
- shouldComponentUpdate
- UNSAFE_componentWillUpdate
该函数将添加 flags,然后再在 commit 阶段触发生命周期方法:
- componentDidUpdate,Flag:
Update; - getSnapshotBeforeUpdate,Flag:
Snapshot;
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
ctor: any,
newProps: any,
renderLanes: Lanes
): boolean {
const instance = workInProgress.stateNode;
cloneUpdateQueue(current, workInProgress);
const unresolvedOldProps = workInProgress.memoizedProps;
const oldProps =
workInProgress.type === workInProgress.elementType
? unresolvedOldProps
: resolveDefaultProps(workInProgress.type, unresolvedOldProps);
instance.props = oldProps;
const unresolvedNewProps = workInProgress.pendingProps;
const oldContext = instance.context;
const contextType = ctor.contextType;
let nextContext = emptyContextObject;
if (typeof contextType === "object" && contextType !== null) {
nextContext = readContext(contextType);
} else if (!disableLegacyContext) {
const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
}
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
typeof getDerivedStateFromProps === "function" ||
typeof instance.getSnapshotBeforeUpdate === "function";
// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillReceiveProps === "function" ||
typeof instance.componentWillReceiveProps === "function")
) {
if (
unresolvedOldProps !== unresolvedNewProps ||
oldContext !== nextContext
) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
nextContext
);
}
}
resetHasForceUpdateBeforeProcessing();
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
newState = workInProgress.memoizedState;
if (
unresolvedOldProps === unresolvedNewProps &&
oldState === newState &&
!hasContextChanged() &&
!checkHasForceUpdateAfterProcessing() &&
!(
enableLazyContextPropagation &&
current !== null &&
current.dependencies !== null &&
checkIfContextChanged(current.dependencies)
)
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === "function") {
if (
unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.flags |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === "function") {
if (
unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.flags |= Snapshot;
}
}
return false;
}
if (typeof getDerivedStateFromProps === "function") {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps
);
newState = workInProgress.memoizedState;
}
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext
) ||
// TODO: In some cases, we'll end up checking if context has changed twice,
// both before and after `shouldComponentUpdate` has been called. Not ideal,
// but I'm loath to refactor this function. This only happens for memoized
// components so it's not that common.
(enableLazyContextPropagation &&
current !== null &&
current.dependencies !== null &&
checkIfContextChanged(current.dependencies));
if (shouldUpdate) {
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillUpdate === "function" ||
typeof instance.componentWillUpdate === "function")
) {
if (typeof instance.componentWillUpdate === "function") {
instance.componentWillUpdate(newProps, newState, nextContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === "function") {
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
}
if (typeof instance.componentDidUpdate === "function") {
workInProgress.flags |= Update;
}
if (typeof instance.getSnapshotBeforeUpdate === "function") {
workInProgress.flags |= Snapshot;
}
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === "function") {
if (
unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.flags |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === "function") {
if (
unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.flags |= Snapshot;
}
}
// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
}
// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
return shouldUpdate;
}
checkShouldComponentUpdate 中将触发 shouldComponentUpdate 生命周期方法;
注意,对于 PureComponent,shouldComponentUpdate 可以不提供实现,此时默认启用 props、state 的浅比较来判断是否抛弃本次更新;
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext
) {
const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === "function") {
if (__DEV__) {
// ...
}
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext
);
if (__DEV__) {
//...
}
return shouldUpdate;
}
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
finishClassComponent
该函数在 Mount 或 Update 之后调用,主要作用:
- 添加 Ref flag,以便在 commit 阶段更新组件 ref;
- 当抛出错误时卸载所有子组件;
- 无错误时,调用生命周期方法 render 获取 ReactDOM(ReactElements),进而执行 reconcile 逻辑;
- 返回 child fiber,作为下一个 unitOfWork;
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderLanes: Lanes
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
const instance = workInProgress.stateNode;
// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== "function"
) {
// If we captured an error, but getDerivedStateFromError is not defined,
// unmount all the children. componentDidCatch will schedule an update to
// re-render a fallback. This is temporary until we migrate everyone to
// the new API.
// TODO: Warn in a future release.
nextChildren = null;
if (enableProfilerTimer) {
// ...
}
} else {
if (__DEV__) {
// ...
} else {
nextChildren = instance.render();
}
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
if (current !== null && didCaptureError) {
// If we're recovering from an error, reconcile without reusing any of
// the existing children. Conceptually, the normal children and the children
// that are shown on error are two different sets, so we shouldn't reuse
// normal children even if their identities match.
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderLanes
);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
// Memoize state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
workInProgress.memoizedState = instance.state;
// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
return workInProgress.child;
}
reconcileChildren
仍然是通过简单的 current 是否为 null 判断当前是 Mount 还是 Update,mountChildFibers 用于创建 ReactDOM 中子元素的 Fiber 节点,reconcileChildren 用于执行 Diff,根据 Diff 结果在 Fiber 节点上添加 flags,commit 阶段将根据 flags 决定如何更新浏览器 DOM.
function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes
) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes
);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes
);
}
}
mountChildFibers 和 reconcileChildFibers 将根据 children 的不同类型(例如 Fragment、Array、纯文本、HTMLElement 等)调用不同的处理逻辑;
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes
): Fiber | null {
// This function is not recursive.
// If the top level item is an array, we treat it as a set of children,
// not as a fragment. Nested arrays on the other hand will be treated as
// fragment nodes. Recursion happens at the normal flow.
// Handle top level unkeyed fragments as if they were arrays.
// This leads to an ambiguity between <>{[...]}</> and <>...</>.
// We treat the ambiguous cases above the same.
const isUnkeyedTopLevelFragment =
typeof newChild === "object" &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// Handle object types
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes
)
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes)
);
case REACT_LAZY_TYPE:
if (enableLazyElements) {
const payload = newChild._payload;
const init = newChild._init;
// TODO: This function is supposed to be non-recursive.
return reconcileChildFibers(
returnFiber,
currentFirstChild,
init(payload),
lanes
);
}
}
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes
);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
lanes
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (typeof newChild === "string" || typeof newChild === "number") {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
"" + newChild,
lanes
)
);
}
if (__DEV__) {
// ...
}
if (typeof newChild === "undefined" && !isUnkeyedTopLevelFragment) {
// If the new child is undefined, and the return fiber is a composite
// component, throw an error. If Fiber return types are disabled,
// we already threw above.
switch (returnFiber.tag) {
case ClassComponent: {
if (__DEV__) {
const instance = returnFiber.stateNode;
if (instance.render._isMockFunction) {
// We allow auto-mocks to proceed as if they're returning null.
break;
}
}
}
// Intentionally fall through to the next case, which handles both
// functions and classes
// eslint-disable-next-lined no-fallthrough
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
invariant(
false,
"%s(...): Nothing was returned from render. This usually means a " +
"return statement is missing. Or, to render nothing, " +
"return null.",
getComponentNameFromFiber(returnFiber) || "Component"
);
}
}
}
// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
[注]:Diff 过程可参考这篇博客:老生常谈 React 的 diff 算法原理-面试版
workLoopConcurrent
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
显然 Concurrent 模式下,与 Legacy 模式唯一的不同点就在于 shouldYield 函数的调用,shouldYield 是 scheduler 导出的函数,简单来说,当时间分片已经耗尽时,就会通过 shouldYield 返回一个 true,使 Work Loop 停止处理,等待下一时间分片。
performUnitOfWork逻辑同上。
FunctionComponent的 Mount 与 Update
鸽。。。下次补上
scheduler
鸽。。。下次补上
备注
- 类组件 state 的更新发生在 mountClassInstance,被赋值为 workInProgress.memoizedState;