Skip to main content

三个阶段

React 更新组件其实可以分为三个阶段:

  • Scheduler。任务优先级分配和调度
  • Render。更新组件内部状态,diff 计算 change
  • Commit。应用 change,执行副作用

Scheduler

该阶段主要是给任务分配优先级,统筹任务调度

Scheduler 的源码解析可以参考:https://juejin.cn/post/6889314677528985614

react 16 基于 requestIdleCallback 的 polyfill 方案来实现任务调度,用法如下:

window.requestIdleCallback(callback[, options])
let handle = window.requestIdleCallback((idleDeadline) => {
const {didTimeout, timeRemaining} = idleDeadline;
console.log(`是否超时?${didTimeout}`);
console.log(`可用时间剩余${timeRemaining.call(idleDeadline)}ms`);
// do sth
const now = Date.now(), timespent = 10;
while (Date.now() < now + timespent);
console.log(`花了${timespent}ms`);
console.log(`可用时间剩余${timeRemaining.call(idleDeadline)}ms`);
}, {timeout: 1000});
// 是否超时?false
// 可用时间剩余45ms
// 花了12ms
// 可用时间剩余32ms

由于 requestIdleCallback 有兼容问题,react 团队采用的是它的 polyfill 方案,可参考

react18

  • 更新时遍历更新每一个节点,每更新一个 Fiber 节点后,会判断累计更新时间是否超过 5ms。
  • 如果超过 5ms,将下一个更新创建为一个宏任务,浏览器自动为其分配执行时机,从而不阻塞用户事件等操作。
  • 如果更新的过程中,用户进行触发了点击事件,那么会在 5ms 与下一个 5ms 的间隙中去执行 click 事件回调

Render

遍历 Fiber 树,得出需要更新的节点信息。可以被打断,让位于优先级更高的操作,比如用户点击等

  1. Fiber Root开始遍历,构建一个新的 Fiber 树。performSyncWorkOnRoot(root)-->renderRootSync
  2. 更新每个 fiber。workLoopSync-->performUnitOfWork-->beginWork
function workLoopSync() {
// workInProgress:当前正在处理的节点
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}

function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
// ...
// NOTE
next = beginWork(current, unitOfWork, subtreeRenderLanes);
// ...
if (next === null) {
// 如果没有子节点,则完成当前工作
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}

beginWork 主要做了以下事情:

  1. 判断 fiber 节点是否可以复用
  2. 根据不同的 Tag(标记不同的组件类型:纯组件、函数组件、类组件),生成不同的 fiber 节点赋值给workInprogress.child
function beginWork(current, workInProgress, renderLanes) {
// ...
switch (workInProgress.tag) {
// ...
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);

// NOTE
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);
// 执行 render()、生命周期钩子函数等 -> reconcileChildren
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case HostRoot:
// ReactDOM.render(<App/>) -> reconcileChildren
return updateHostRoot(current, workInProgress, renderLanes);
// ...
}

updateFunctionComponent为例,主要干了几件事:

  1. 生成新的 hooks 链表并挂载在 fiber 节点上
  2. 调用 reconcileChildren
  3. 返回 workInprogress.child
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes
) {
// ...
{
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes
);
}
// ...
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
// ...

reconcileChildren主要是处理子 fiber 节点,在这个阶段做 diff,并在变更的节点上标记副作用类型

// 处理子 fiber 节点
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
// ...
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
const isUnkeyedTopLevelFragment = ...;
// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
// 根据不同的类型,处理不同的节点对比
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
...
}

if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes,
),
);
}
// 多节点数组
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
...
}
...
// reconcileChildFibers 会判断节点变更的类型,赋值flags
// 在commit阶段,根据flags更新dom
// placeSingleChild 会先判断是否是第一次渲染,是的话会增加Placement副作用
function placeSingleChild(newFiber: Fiber): Fiber {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags = Placement;
}
return newFiber;
}
...
// 对于 ReactElement,会调用 reconcileSingleElement
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
// 新 key
const key = element.key;
// 旧的第一个子节点
let child = currentFirstChild;

while (child !== null) {
// 新旧key相同
if (child.key === key) {
// 节点类型未改变
if (
child.tag === Fragment
? element.type === REACT_FRAGMENT_TYPE
: child.elementType === element.type ||
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false)
) {
// 复用当前 child,先删除它的兄弟节点
deleteRemainingChildren(returnFiber, child.sibling);
// 复制 fiber 节点,重置 index 和 sibling
const existing = useFiber(
child,
element.type === REACT_FRAGMENT_TYPE
? element.props.children
: element.props,
expirationTime,
);
// ...
// 设置父节点
existing.return = returnFiber;
return existing;
} else {
// 节点类型不同,要删除旧节点
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
// 新旧 key 不相同,直接删除
deleteChild(returnFiber, child);
}
child = child.sibling;
}

// child 为 null,需要建立子节点
if (element.type === REACT_FRAGMENT_TYPE) {
// 创建 Fragment 类型的 fiber 节点
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
expirationTime,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 创建 Element 类型的 fiber 节点
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}

...
// 副作用类型
import {
NoEffect,
PerformedWork,
Placement, // 挂载,didMount
Update, // 更新, didUpdate
Snapshot, // getSnapshotBeforeUpdate,更新之前设置快照
PlacementAndUpdate,
Deletion, // 卸载,willUnmount
ContentReset,
Callback,
DidCapture,
Ref,
Incomplete,
HostEffectMask,
Passive,
} from'shared/ReactSideEffectTags';
  1. fiber 节点更新收尾,串联 fiber 节点,收集 effect(如果有副作用)。completeUnitOfWorkcompleteWork
function completeUnitOfWork(unitOfWork: Fiber) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
if ((completedWork.flags & Incomplete) === NoFlags) {
let next;
// 处理 fiber 的 props、创建 dom 对象、绑定事件等
next = completeWork(current, completedWork, subtreeRenderLanes);
// ...
// 收集 effect,把子节点 side Effect 加到父节点的 side Effect 上
if (
returnFiber !== null &&
(returnFiber.flags & Incomplete) === NoFlags
) {
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;
}

const flags = completedWork.flags;
if (flags > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
} else {
// ...
}
// 存在兄弟节点,将 workInProgress 指向兄弟节点,并return,执行兄弟节点的beginWork -> Fiber Node
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
// 没有兄弟节点,返回父节点
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
// ...
}

收集 effect 时,会创建 effectList,如下图所示,橙色节点上有 effect

commit

执行到 commitRoot 时,意味着 render 阶段结束,进入 commit 阶段。该阶段主要是处理 FiberRoot 上收集的 effectList,只针对变化的节点做工作,并执行生命周期钩子函数

先看下几个生命周期的执行时机:

// 第1阶段 render/reconciliation
componentWillMount;
componentWillReceiveProps;
shouldComponentUpdate;
componentWillUpdate;

// 第2阶段 commit
componentDidMount;
componentDidUpdate;
componentWillUnmount;

第 1 阶段的生命周期函数可能会被多次调用,默认以低优先级执行,被高优先级任务打断后,稍后会重新执行

commitRoot 主要是执行commitRootImpl这个函数

function commitRoot(root) {
// ...
commitRootImpl.bind(null, root, renderPriorityLevel)
// ...
}
// ...
function commitRootImpl() {
do {
// NOTE 调用 flushPassiveEffects 执行完所有 effect
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
// ...
firstEffect = finishedWork.firstEffect;
if (firseEffect !== null) {
nextEffect = firstEffect;
// 第一阶段,before mutation
do {
commitBeforeMutationEffects();
} while(nextEffect !== null)
// ...
// 重置 nextEffect 为 firstEffect,接着进行第二阶段
nextEffect = firstEffect;
// 第二阶段 mutation
do {
commitMutationEffects(root, renderPriorityLevel);
} while(nextEffect !== null)

// 将当前的 workInProgress树 作为 current 树
root.current = finishedWork;

// ...
// 第三阶段 layout
do {
commitLayoutEffects(root, expirationTime);
} while(nextEffect)
// ...
// 确保 root 上所有的 work 都被调度完
ensureRootIsScheduled(root);
// 检测在 useLayoutEffect 中是否做了布局修改等,刷新布局,如果在 layoutEffect 中调用了 setState 也会在该函数中检测中并开启新的一轮调度
flushSyncCallbackQueue();
}
...
}

commit 阶段可以细分为以下三个阶段:

  1. before mutation:读取组件变更前的状态,对于类组件,会调用生命周期函数getSnapshotBeforeUpdate,在 DOM 变更前可以获取到组件实例相关的信息;对于函数组件,会异步调度 useEffect
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
// 对于使用 getSnapShowBeforeUpdate 的组件 fiber.effectTag |= SnapShot
if ((effectTag & Snapshot) !== NoEffect) {
// ...
const current = nextEffect.alternate;
// 执行 getSnapShotBeforeUpdate 生命周期
commitBeforeMutationEffectOnFiber(current, nextEffect);
// ...
}
// 对于使用 useEffect 的组件,其 Fiber.effectTag = UpdateEffect | PassiveEffect
if ((effectTag & Passive) !== NoEffect) {
// If there are passive effects, schedule a callback to flush at
// the earliest opportunity.
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
// NOTE
scheduleCallback(NormalPriority, () => {
flushPassiveEffects();
return null;
});
}
}
nextEffect = nextEffect.nextEffect;
}
}
  1. mutation:根据 effectTag 执行对应的 dom 操作。对于类组件,还会调用 componentWillUnmount;对于函数组件,还会执行 useLayoutEffect 的销毁函数
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
while (nextEffct) {
// ...
const effectTag = nextEffect.effectTag;
let primaryEffectTag =
effectTag & (Placement | Update | Deletion | Hydrating);
// ...
switch (primaryEffectTag) {
// 挂载 DOM
case Placement: {
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
return;
}
// 更新组件及DOM
case PlacementAndUpdate: {
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
// 刷新 layoutEffect.destroy
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 更新组件
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 卸载
case Deletion: {
commitDeletion(root, nextEffect, renderPriorityLevel);
break;
}
// ...
}
nextEffect = nextEffect.nextEffect;
}
}
  1. layout:在 DOM 操作完成后,读取组件的状态,对于类组件,会调用生命周期componentDidMountcomponentDidUpdate;对于函数式组件,主要执行 commitHookEffectListMount
function commitLayoutEffects(root, committedExpirationTime) {
while (nextEffect !== null) {
// ...
const effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
recordEffect();
const current = nextEffect.alternate;
// didmount
commitLayoutEffectOnFiber(
root,
current,
nextEffect,
committedExpirationTime
);
}
// ...
}
}

function commitLayoutEffectOnFiber(
finishedRoot,
current,
finishedWork,
committedExpirationTime
) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
// ...
return;
}
case ClassComponent: {
// didMount
}
}
}