React Advanced Notes
React Core Packages
Scheduler
调度器: 调度任务的优先级, 高优任务优先进入Reconciler
.Reconciler
协调器:- 装载
Renderer
. - 接收
ReactDOM
和React
模块 (用户代码) 发起的更新请求:ReactFiberReconciler.updateContainer
.ReactFiberClassComponent.setState
.ReactFiberHooks.dispatchAction
.
- 找出变化组件, 构建 Fiber Tree.
- 装载
Renderer
渲染器:- 引导
React
应用启动 (e.gReactDOM.createRoot(rootNode).render(<App />)
). - 实现
HostConfig
协议, 将变化的组件渲染到页面上.
- 引导
其中 Reconciler
构建 Fiber Tree 的过程被包装成一个回调函数, 传入 Scheduler
模块等待调度.
Scheduler
将回调函数进一步包装成任务对象, 放入多优先级调度的任务队列, 循环消费任务队列, 直至队列清空.
Scheduler Work Loop (任务调度循环) 负责调度 Task
,
Reconciler Work Loop (Fiber
构造循环) 负责实现 Task
.
React
runtime main logic:
- Updates:
Add
/Delete
/Mutation
updates fromUser Code
. - Registration:
Reconciler
receive updates request fromUser Code
.Scheduler
register newTask
.
- Execution:
Scheduler
consumeTask
inTaskQueue
in work loop.Reconciler
executeTask
work.Fiber
构造循环: constructFiber
tree.commitRoot
: renderFiber
tree withRenderer
.
- 任务调度循环与
Fiber
构造循环相互配合可实现可中断渲染:- 渲染中断 (
Reconciler.renderRootConcurrent().shouldYield()
):- 存在更高优先级任务 (Priority Scheduling).
- 当前帧没有剩余时间 (Time Slicing).
- 渲染恢复 (
Scheduler.workLoop()
): 将callback()
返回的任务放入任务队列, 继续进行调度直至清空任务队列.
- 渲染中断 (
React Virtual DOM
- Reduce rendering times with reconciliation algorithm,
improving rendering efficiency:
Declarative UI performance = Diff performance + DOM performance,
Virtual DOM
主要是为了最小化 Diff 性能消耗. - Cross platform code.
- Functional programming without details on DOM manipulation.
- Virtual DOM 很多时候都不是最优的操作, 但它具有普适性, 在效率与可维护性之间达到平衡.
- SnabbDOM: virtual DOM library focus on modularity and performance.
React Core Workflow
Create RootContainer
Legacy Root
- react-dom/src/client/ReactDOMLegacy:
- render.
- legacyRenderSubtreeIntoContainer.
- legacyCreateRootFromDOMContainer.
- react-reconciler/src/ReactFiberReconciler:
- createContainer.
- react-dom/src/client/ReactDOMComponentTree:
- markContainerAsRoot.
- react-reconciler/src/ReactFiberRoot:
- createFiberRoot.
- react-reconciler/src/ReactFiber:
- createHostRootFiber.
- react-reconciler/src/ReactUpdateQueue:
- initializeUpdateQueue.
- react-dom/src/events/DOMPluginEventSystem:
- listenToAllSupportedEvents: 事件统一在 rootContainer 上处理 dispatchDiscreteEvent.
Concurrent Root
- react-dom/src/client/ReactDOMRoot:
- createRoot.
- react-reconciler/src/ReactFiberReconciler:
- createContainer.
- react-dom/src/client/ReactDOMComponentTree:
- markContainerAsRoot.
- react-reconciler/src/ReactFiberRoot:
- createFiberRoot.
- react-reconciler/src/ReactFiber:
- createHostRootFiber.
- react-reconciler/src/ReactUpdateQueue:
- initializeUpdateQueue.
- react-dom/src/events/DOMPluginEventSystem:
- listenToAllSupportedEvents: 事件统一在 rootContainer 上处理 dispatchDiscreteEvent.
ReactDOMRoot.render(<App />)
.
Update RootContainer
- react-dom/src/client/ReactDOMLegacy:
- render.
- legacyRenderSubtreeIntoContainer.
- react-dom/src/client/ReactDOMRoot:
- render.
- react-reconciler/src/ReactFiberReconciler:
- updateContainer.
- react-reconciler/src/ReactUpdateQueue:
- createUpdate.
- enqueueUpdate.
- react-reconciler/src/ReactFiberWorkLoop:
- scheduleUpdateOnFiber.
- ensureRootIsScheduled.
- react-reconciler/src/ReactFiberSyncTaskQueue:
- flushSyncCallbacks.
- react-reconciler/src/ReactFiberWorkLoop:
- performSyncWorkOnRoot.
- renderRootSync.
- workLoopSync.
- performUnitOfWork.
- react-reconciler/src/ReactFiberBeginWork:
- beginWork.
- updateHostRoot/updateXXXComponent.
ReactDOMComponent.createElement
.- reconcileChildren.
- react-reconciler/src/ReactChildFiber:
- reconcileChildFibers.
- react-reconciler/src/ReactFiberWorkLoop:
- completeUnitOfWork.
- react-reconciler/src/ReactFiberCompleteWork
- completeWork.
- react-reconciler/src/ReactFiberWorkLoop:
- commitRoot.
- react-dom/src/client/ReactDOMHostConfig:
- appendChildToContainer.
- finalizeInitialChildren.
- react-dom/src/client/ReactDOMComponent:
- setInitialProperties: 设置初始化属性, 处理特殊元素和事件.
// Legacy Mode
import type { ReactElement } from 'react';
import Reconciler from './reconciler';
import type { Container } from './types';
const Renderer = {
render: (
element: ReactElement,
container: Container | null,
callback?: Function
): void => {
if (container) {
const root = Reconciler.createContainer(container, 0, false, null);
Reconciler.updateContainer(element, root, null);
}
},
};
export default Renderer;
// Modern Mode
import type { ReactElement } from 'react';
import Reconciler from './reconciler';
import type { Container, OpaqueRoot } from './types';
const Renderer = {
createRoot: (
container: Container | null,
callback?: Function
): OpaqueRoot => {
if (container) {
const root = Reconciler.createContainer(container, 0, false, null);
root.render = function (element: ReactElement) {
Reconciler.updateContainer(element, this, null);
};
return root;
}
},
};
export default Renderer;
ReactComponent SetState
- react-dom/src/events/ReactDOMEventListener:
- dispatchDiscreteEvent.
- react/src/ReactBaseClasses:
- setState.
- react-reconciler/src/ReactFiberClassComponent:
- enqueueSetState.
- react-reconciler/src/ReactUpdateQueue:
- createUpdate.
- enqueueUpdate.
- react-reconciler/src/ReactFiberWorkLoop:
- scheduleUpdateOnFiber.
- discreteUpdates.
- react-reconciler/src/ReactFiberSyncTaskQueue:
- flushSyncCallbacks.
- react-reconciler/src/ReactFiberWorkLoop:
- performSyncWorkOnRoot.
- workLoopSync.
- performUnitOfWork.
- react-reconciler/src/ReactFiberBeginWork:
- beginWork.
- updateXXXComponent.
- reconcileChildren.
- react-reconciler/src/ReactChildFiber:
- reconcileChildFibers.
- react-reconciler/src/ReactFiberWorkLoop:
- completeUnitOfWork.
- react-reconciler/src/ReactFiberCompleteWork
- completeWork.
- react-reconciler/src/ReactFiberWorkLoop:
- commitRoot.
- commitMutationEffects.
- react-reconciler/src/ReactFiberCommitWork:
- commitWork.
- react-dom/src/client/ReactDOMHostConfig:
- commitUpdate.
- react-dom/src/client/ReactDOMComponentTree:
- updateFiberProps.
- react-dom/src/client/ReactDOMComponent:
- updateProperties: Apply the diff.
ClassComponent Update
- react-reconciler/src/ReactFiberWorkLoop:
- performSyncWorkOnRoot.
- workLoopSync.
- performUnitOfWork.
- react-reconciler/src/ReactFiberBeginWork:
- beginWork
- updateClassComponent.
- react-reconciler/src/ReactFiberClassComponent:
- updateClassInstance.
- react-reconciler/src/ReactFiberBeginWork:
- finishClassComponent.
- instance.render (User defined Component).
- reconcileChildren.
- react-reconciler/src/ReactChildFiber:
- reconcileChildFibers.
FunctionComponent Update
- react-reconciler/src/ReactFiberWorkLoop:
- performSyncWorkOnRoot.
- workLoopSync.
- performUnitOfWork.
- react-reconciler/src/ReactFiberBeginWork:
- beginWork.
- updateFunctionComponent.
- react-reconciler/src/ReactFiberHooks:
- renderWithHooks.
- FunctionComponent() (User defined Function).
- Hooks: useXXX -> mountXXX -> updateXXX.
- react-reconciler/src/ReactFiberBeginWork:
- reconcileChildren.
- react-reconciler/src/ReactChildFiber:
- reconcileChildFibers.
React Scheduler
Work loop in scheduler focus on Task Scheduling,
not only including Reconciler.performSyncWorkOnRoot
/Reconciler.performConcurrentWorkOnRoot
,
but also for non-react tasks
(meaning Scheduler
module can work standalone without React
).
Scheduler Priority
React 16, unstable concurrent mode with
Priorities
:
- ImmediatePriority: 立即执行优先级, 级别最高,
expirationTime = -1
. - UserBlockingPriority: 用户阻塞优先级,
expirationTime = 250
. - NormalPriority: 正常优先级,
expirationTime = 5000
. - LowPriority: 低优先级,
expirationTime = 10000
. - IdlePriority: 可闲置优先级,
expirationTime = maxSigned31BitInt
.
React 17, stable concurrent mode with
Lanes
:
export type Lanes = number;
export type Lane = number;
export const TotalLanes = 31;
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lanes = /* */ 0b0000000000000000000000000000100;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000;
export const DefaultLane: Lanes = /* */ 0b0000000000000000000000000010000;
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000;
const RetryLanes: Lanes = /* */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /* */ 0b0000100000000000000000000000000;
export const SomeRetryLane: Lane = RetryLane1;
export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
const NonIdleLanes = /* */ 0b0001111111111111111111111111111;
export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000;
export const IdleLane: Lanes = /* */ 0b0100000000000000000000000000000;
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
Scheduler Workflow
Scheduler main workflow:
scheduleCallback(callback)
-> push(queue, newTask)
(Wrap callback
into task
)
(For delayed task -> requestHostTimeout(handleTimeout, delayTime)
)
-> requestHostCallback(flushWork)
-> messageChannelPort.postMessage(null)
-> performWorkUntilDeadline()
-> flushWork(hasTimeRemaining, currentTime)
:
-> workLoop(hasTimeRemaining, currentTime)
:
将 Reconciler 的工作 (Callback) 包装成 Task 组成 Task Queue, 按照时间分片机制, 不断地消费 Task Queue.
对于延时任务 (Delayed Task), 会将其先放入 Timer Queue, 等待延时完成后再将其放入 Task Queue.
Scheduler Time Slicing
// 时间切片周期, 默认是 5ms.
// 如果一个 task 运行超过该周期, 下一个 task 执行前, 会把控制权归还浏览器.
const yieldInterval = 5;
const maxYieldInterval = 300;
let deadline = 0; // currentTime + yieldInterval.
let needsPaint = false;
let isMessageLoopRunning = false;
let scheduledHostCallback = null;
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
const scheduling = navigator.scheduling;
const getCurrentTime = performance.now;
// 请求回调:
const requestHostCallback = callback => {
// 1. 保存 callback.
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
// 2. 通过 MessageChannel 发送消息.
port.postMessage(null);
}
};
// 取消回调:
const cancelHostCallback = () => {
scheduledHostCallback = null;
};
const requestHostTimeout = (callback, ms) => {
taskTimeoutID = setTimeout(() => {
callback(getCurrentTime());
}, ms);
};
const cancelHostTimeout = () => {
clearTimeout(taskTimeoutID);
taskTimeoutID = -1;
};
// 是否让出主线程 (time slice):
const shouldYieldToHost = () => {
const currentTime = getCurrentTime();
if (currentTime >= deadline) {
if (needsPaint || scheduling.isInputPending()) {
// There is either a pending paint or a pending input.
return true;
}
// There's no pending input.
// Only yield if we've reached the max yield interval.
return currentTime >= maxYieldInterval;
} else {
// There's still time left in the frame.
return false;
}
};
// 请求绘制:
const requestPaint = () => {
needsPaint = true;
};
// 实际回调函数处理:
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
// 1. 设置 currentTime 与 deadline.
const currentTime = getCurrentTime();
deadline = currentTime + yieldInterval;
const hasTimeRemaining = true;
try {
// 2. 执行回调, 返回是否有还有剩余任务.
const hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
if (!hasMoreWork) {
// 没有剩余任务, 退出.
isMessageLoopRunning = false;
scheduledHostCallback = null;
} else {
port.postMessage(null); // 有剩余任务, 发起新的调度.
}
} catch (error) {
port.postMessage(null); // 如有异常, 重新发起调度.
throw error;
}
} else {
isMessageLoopRunning = false;
}
needsPaint = false; // Reset.
};
Scheduler Task Queue
Task queue is MinHeap, storing Tasks.
const newTask = {
id: taskIdCounter++,
callback, // Work from reconciler.
priorityLevel,
startTime,
expirationTime,
sortIndex: -1, // MinHeap queue indexing.
};
const scheduleCallback = (priorityLevel, callback, options) => {
const currentTime = getCurrentTime();
const startTime = currentTime;
const expirationTime = startTime + timeout[priorityLevel]; // -1/250/5000/10000/MAX_INT.
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (startTime > currentTime) {
// Delayed task.
newTask.sortIndex = startTime;
push(timerQueue, newTask);
// All tasks are delayed, and this is the task with the earliest delay.
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// Schedule a timeout.
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// Normal task.
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
};
const handleTimeout = currentTime => {
isHostTimeoutScheduled = false;
advanceTimers(currentTime);
if (!isHostCallbackScheduled) {
if (peek(taskQueue) !== null) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
}
}
};
Scheduler Work Loop
当 callback()
返回函数时, 表明产生连续回调 (e.g 出现更高优先任务/时间分片用完, 渲染中断),
需将返回的函数再次放入任务队列, 继续进行调度直至清空任务队列 (渲染恢复).
function flushWork(hasTimeRemaining, initialTime) {
// We'll need a host callback the next time work is scheduled.
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
// We scheduled a timeout but it's no longer needed. Cancel it.
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
isPerformingWork = true; // Lock.
const previousPriorityLevel = currentPriorityLevel;
try {
return workLoop(hasTimeRemaining, initialTime);
} finally {
// Restore context.
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
isPerformingWork = false;
}
}
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (currentTask !== null) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// This currentTask hasn't expired, and we've reached the deadline.
break;
}
const callback = currentTask.callback;
if (typeof callback === 'function') {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
// 产生了连续回调 (如 Fiber树太大, 出现了中断渲染), 保留 currentTask.
currentTask.callback = continuationCallback;
} else {
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
} else {
// 如果任务被取消 (currentTask.callback = null), 将其移出队列.
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
// Return whether there's additional work.
if (currentTask !== null) {
return true;
} else {
const firstTimer = peek(timerQueue);
// 存在延时任务, 继续进行调度.
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
React Fiber
React Fiber 的目标是提高其在动画、布局和手势等领域的适用性.
它的主要特性是 Incremental Rendering
: 将渲染任务拆分为小的任务块并将任务分配到多个帧上的能力.
React Fiber Type
Fiber
definition:
- Component type.
- Current props and state.
- Pointers to parent, sibling, and child components,
- Pointer to DOM/class instance.
- Other internal metadata to track rendering process.
export interface Fiber {
tag: WorkTag;
key: string | null;
elementType: any;
type: any; // Tag/Class/Function.
stateNode: any; // DOM/class instance.
ref: (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject | null;
// Singly Linked List Tree Structure.
return: Fiber | null; // DFS parent Fiber node.
child: Fiber | null;
sibling: Fiber | null;
index: number;
// Props and state for output.
pendingProps: any;
memoizedProps: any;
updateQueue: mixed; // Updates from diff(pendingProps, memoizedProps).
memoizedState: any;
// Context API.
dependencies: Dependencies | null; // (Contexts, Events) dependencies.
mode: TypeOfMode; // NoMode/BlockingMode/ConcurrentMode bit.
// Effects.
flags: Flags;
subtreeFlags: Flags;
deletions: Array<Fiber> | null;
nextEffect: Fiber | null; // Next effect Fiber node.
firstEffect: Fiber | null; // First effect Fiber node.
lastEffect: Fiber | null; // Last effect Fiber node.
// Priority.
lanes: Lanes;
childLanes: Lanes;
alternate: Fiber | null; // `current` Fiber and `workInpProgress` Fiber.
// Performance statistics for React DevTool.
actualDuration?: number;
actualStartTime?: number;
selfBaseDuration?: number;
treeBaseDuration?: number;
}
React Fiber Work Tag
常见的 Fiber 类型:
- HostComponent: HTML native tag.
- ClassComponent.
- FunctionComponent.
type WorkTag =
| 'FunctionComponent'
| 'ClassComponent'
| 'IndeterminateComponent'
| 'HostRoot'
| 'HostPortal'
| 'HostComponent'
| 'HostText'
| 'Fragment'
| 'Mode'
| 'ContextConsumer'
| 'ContextProvider'
| 'ForwardRef'
| 'Profiler'
| 'SuspenseComponent'
| 'MemoComponent'
| 'SimpleMemoComponent'
| 'LazyComponent'
| 'IncompleteClassComponent'
| 'DehydratedFragment'
| 'SuspenseListComponent'
| 'FundamentalComponent'
| 'ScopeComponent'
| 'Block'
| 'OffscreenComponent'
| 'LegacyHiddenComponent';
React Fiber Mode
React 运行模式:
所有 Fiber.mode
保持一致 (包括 FiberRoot
).
type TypeOfMode = number;
const NoMode = /* */ 0b000000;
const ConcurrentMode = /* */ 0b000001;
const ProfileMode = /* */ 0b000010;
const DebugTracingMode = /* */ 0b000100;
const StrictLegacyMode = /* */ 0b001000;
const StrictEffectsMode = /* */ 0b010000;
const ConcurrentUpdatesByDefaultMode = /* */ 0b100000;
React Fiber Effects
- Insert DOM elements:
Placement
tag. - Update DOM elements:
Update
tag. - Delete DOM elements:
Deletion
tag. - Update Ref property:
Ref
tag. useEffect
callback:got Passive
tag.useEffect(fn)
:Mount
andUpdate
lifecycle.useEffect(fn, [])
:Mount
lifecycle.useEffect(fn, [deps])
:Mount
lifecycle andUpdate
lifecycle withdeps
changed.
React create effects when Render
stage,
then update effects to real DOM when Commit
stage.
常见的 Effect 标志位:
type Flags = number;
const NoFlags = /* */ 0b000000000000000000;
const PerformedWork = /* */ 0b000000000000000001;
const Placement = /* */ 0b000000000000000010;
const Update = /* */ 0b000000000000000100;
const PlacementAndUpdate = /* */ 0b000000000000000110;
const Deletion = /* */ 0b000000000000001000;
const ContentReset = /* */ 0b000000000000010000;
const Callback = /* */ 0b000000000000100000;
const DidCapture = /* */ 0b000000000001000000;
const Ref = /* */ 0b000000000010000000;
const Snapshot = /* */ 0b000000000100000000;
const Passive = /* */ 0b000000001000000000;
const PassiveUnmountPendingDev = /* */ 0b000010000000000000;
const Hydrating = /* */ 0b000000010000000000;
const HydratingAndUpdate = /* */ 0b000000010000000100;
const LifecycleEffectMask = /* */ 0b000000001110100100;
const HostEffectMask = /* */ 0b000000011111111111;
const Incomplete = /* */ 0b000000100000000000;
const ShouldCapture = /* */ 0b000001000000000000;
const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000;
const PassiveStatic = /* */ 0b001000000000000000;
const BeforeMutationMask = /* */ 0b000000001100001010;
const MutationMask = /* */ 0b000000010010011110;
const LayoutMask = /* */ 0b000000000010100100;
const PassiveMask = /* */ 0b000000001000001000;
const StaticMask = /* */ 0b001000000000000000;
const MountLayoutDev = /* */ 0b010000000000000000;
const MountPassiveDev = /* */ 0b100000000000000000;
React Fiber Lanes
- Legacy 模式: 返回 SyncLane.
- Blocking 模式: 返回 SyncLane.
- Concurrent 模式:
- 正常情况: 根据当前的调度优先级来生成一个 lane.
- 处于 Suspense 过程中: 会优先选择
TransitionLanes
通道中的空闲通道 (或最高优先级).
export function requestUpdateLane(fiber: Fiber): Lane {
const mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
// Legacy 模式.
return SyncLane;
} else if ((mode & ConcurrentMode) === NoMode) {
// Blocking 模式.
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
? SyncLane
: SyncBatchedLane;
}
// Concurrent 模式.
if (currentEventWipLanes === NoLanes) {
currentEventWipLanes = workInProgressRootIncludedLanes;
}
const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
// 特殊情况, 处于 Suspense 过程中.
if (currentEventPendingLanes !== NoLanes) {
currentEventPendingLanes =
mostRecentlyUpdatedRoot !== null
? mostRecentlyUpdatedRoot.pendingLanes
: NoLanes;
}
return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
}
// 正常情况, 获取调度优先级.
let lane;
const schedulerPriority = getCurrentPriorityLevel();
if (
(executionContext & DiscreteEventContext) !== NoContext &&
schedulerPriority === UserBlockingSchedulerPriority
) {
// `executionContext` 存在输入事件, 且调度优先级是用户阻塞性质.
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
// 调度优先级转换为车道模型.
const schedulerLanePriority =
schedulerPriorityToLanePriority(schedulerPriority);
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
}
return lane;
}
Fiber 树构造过程中 (Render Phase
),
若 Fiber 对象或 Update 对象优先级 (fiber.lanes
/update.lane
) 比全局渲染优先级低,
则将会被忽略 (节点未更新, 可以直接复用).
export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
const pendingLanes = root.pendingLanes;
if (pendingLanes === NoLanes) {
return NoLanes;
}
let nextLanes = NoLanes;
const suspendedLanes = root.suspendedLanes;
const pingedLanes = root.pingedLanes;
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
} else {
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
}
}
} else {
const unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
} else {
if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
}
}
}
if (nextLanes === NoLanes) {
return NoLanes;
}
if (
wipLanes !== NoLanes &&
wipLanes !== nextLanes &&
(wipLanes & suspendedLanes) === NoLanes
) {
const nextLane = getHighestPriorityLane(nextLanes);
const wipLane = getHighestPriorityLane(wipLanes);
if (
nextLane >= wipLane ||
(nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
) {
return wipLanes;
}
}
if (
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Do nothing, use the lanes as they were assigned.
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
nextLanes |= pendingLanes & DefaultLane;
}
const entangledLanes = root.entangledLanes;
if (entangledLanes !== NoLanes) {
const entanglements = root.entanglements;
let lanes = nextLanes & entangledLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
nextLanes |= entanglements[index];
lanes &= ~lane;
}
}
return nextLanes;
}
Lanes model use case:
// task 与 batchTask 的优先级是否重叠:
// 1. expirationTime:
const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch;
// 2. Lanes:
const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
// 当同时处理一组任务, 该组内有多个任务, 且每个任务的优先级不一致:
// 1. expirationTime:
const isTaskIncludedInBatch =
taskPriority <= highestPriorityInRange &&
taskPriority >= lowestPriorityInRange;
// 2. Lanes:
const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
// 从 group 中增删 task:
// 1. expirationTime (need list):
task.prev.next = task.next;
let current = queue;
while (task.expirationTime >= current.expirationTime) {
current = current.next;
}
task.next = current.next;
current.next = task;
const isTaskIncludedInBatch =
taskPriority <= highestPriorityInRange &&
taskPriority >= lowestPriorityInRange;
// 2. Lanes:
batchOfTasks &= ~task; // Delete task.
batchOfTasks |= task; // Add task.
const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
React Fiber Trees
current
Fiber tree: rendered to screen.workInProgress
Fiber tree: under reconciliation.- When
workInProgress
Fiber tree completerender
+commit
, swap 2 Fiber tree:- Reuse Fiber objects.
- Reduce memory usage and GC time.
FiberRoot
:FiberRoot.current = currentHostRootFiber
.FiberRoot.finishedWork = workInProgressHostRootFiber
.currentHostRootFiber.stateNode = FiberRoot
.workInProgressHostRootFiber.stateNode = FiberRoot
.currentHostRootFiber.alternate = workInProgressHostRootFiber
workInProgressHostRootFiber.alternate = currentHostRootFiber
ReactElement
tree ->Fiber
tree ->DOM
tree.
React Fiber Work Loop
React Reconciler
Reconciler Render Workflow
Reconciler construct Fiber tree:
- scheduleUpdateOnFiber:
- 首次 render 直接调用
performWorkOnRoot
. - 再次 render 需要调用
ensureRootIsScheduled
.
- 首次 render 直接调用
- ensureRootIsScheduled.
- flushSyncCallbacks.
- performSyncWorkOnRoot / performConcurrentWorkOnRoot:
performConcurrentWorkOnRoot
支持可中断渲染:- 此函数首先检查是否处于 render 过程中, 是否需要恢复上一次渲染.
- 如果本次渲染被中断,
此函数最后返回一个新的
performConcurrentWorkOnRoot
函数, 等待下一次 Scheduler 调度.
- renderRootSync / renderRootConcurrent:
- 此函数会调用
prepareFreshStack
, 重置 FiberRoot 上的全局属性, 重置 Fiber Work Loop 全局变量. - 此函数会设置
workInProgressRoot = FiberRoot
, 表示正在进行 render. - 此函数退出前, 会重置
workInProgressRoot = null
, 表示没有正在进行中的 render. - 此函数退出前, 会挂载
FiberRoot.finishedWork = workInProgressHostRootFiber
. 此时HostRootFiber
上挂载了副作用队列, 层级越深子节点副作用越靠前.
- 此函数会调用
- workLoopSync / workLoopConcurrent:
循环调用
performUnitOfWork
, 直到workInProgress === null
或用完当前时间分片. - performUnitOfWork(workInProgress):
- 存在子节点,
beginWork
与completeUnitOfWork
不在同一次循环里调用: 执行完beginWork
后, 优先向下遍历, 执行子节点的beginWork
与completeUnitOfWork
, 在 N 次循环后再向上回溯. - 不存在子节点,
beginWork
与completeUnitOfWork
在同一次循环里调用. - 若
beginWork
返回next
节点, 则设置workInProgress = next
进行 DFS 遍历, 再次调用此函数. - 若
beginWork
返回null
节点, 则调用completeUnitOfWork
函数完成节点处理. - 若存在兄弟节点,
completeUnitOfWork
会设置workInProgress = siblingFiber
进行 DFS 遍历, 再次调用此函数. - 若到达子叶节点,
completeUnitOfWork
会设置workInProgress = returnFiber
进行 DFS 回溯, 再次调用此函数.
- 存在子节点,
- beginWork:
- 根据
ReactElement
对象创建所有的 Fiber 节点, 最终构造出 Fiber 树形结构 (设置return
和sibling
指针). - 调用
updateXXX
, 设置fiber.flags
/fiber.stateNode
等状态. - 非子叶节点返回子节点, 进行 DFS 遍历; 子叶节点返回
null
, 直接进入completeUnitOfWork
阶段.
- 根据
- updateHostRoot/updateXXXComponent:
- 根据
fiber.pendingProps
/fiber.updateQueue
等输入数据状态, 计算fiber.memoizedState
作为输出状态. - ClassComponent:
- 构建
React.Component
实例. - 把新实例挂载到
fiber.stateNode
上. - 执行
render
之前的生命周期函数. - 执行
render
方法, 获取下级ReactElement
. - 设置
fiber.flags
, 标记副作用.
- 构建
- FunctionComponent:
- 执行
renderWithHooks()
->FunctionComponent()
, 获取下级ReactElement
. - 设置
fiber.flags
, 标记副作用.
- 执行
- HostComponent.
pendingProps.children
作为下级ReactElement
.- 如果下级节点是文本节点, 则设置下级节点为
null
(进入completeUnitOfWork
阶段). - 设置
fiber.flags
, 标记副作用.
- 根据实际情况, 设置
fiber.flags
, 标记副作用. - 根据获取的下级
ReactElement
对象, 调用reconcileChildren
生成Fiber
子节点 (只生成次级子节点).
- 根据
ReactDOMComponent.createElement()
/ReactClassComponent.render()
/ReactFunctionComponent()
.- reconcileChildren.
- mountChildFibers/reconcileChildFibers:
mountChildFibers
: similar logic, not tracking side effects.reconcileChildFibers
: similar logic, tracking side effects.reconcileSingleElement
.reconcileSingleTextNode
.reconcileSinglePortal
.reconcileChildrenArray
.reconcileChildrenIterator
.
- completeUnitOfWork:
- 当
reconcileChildren
返回值为null
时, 表示 DFS 进行到子叶节点,performUnitOfWork
会调用completeUnitOfWork
函数. - 调用
completeWork
进行render
. - 把当前 Fiber 对象的副作用队列 (
firstEffect
与lastEffect
) 加到父节点的副作用队列之后, 更新父节点的firstEffect
和lastEffect
指针. - 识别
beginWork
阶段设置的fiber.flags
, 若当前 Fiber 存在副作用 (Effects), 则将当前 Fiber 加入到父节点的 Effects 队列, 等待 Commit 阶段处理. - 将
workInProgress
设置为siblingFiber
(DFS 遍历) 或returnFiber
(DFS 回溯), 继续构建 Fiber 树.
- 当
- completeWork:
- 创建 DOM 实例, 绑定至
HostComponent
/HostText
fiber.stateNode
(局部状态). - 设置 DOM 节点属性, 绑定事件.
- 设置
fiber.flags
, 收集副作用.
- 创建 DOM 实例, 绑定至
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number
) {
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (lane === SyncLane) {
if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// 初次渲染.
performSyncWorkOnRoot(root);
} else {
// 对比更新.
ensureRootIsScheduled(root, eventTime);
}
}
mostRecentlyUpdatedRoot = root;
}
function performSyncWorkOnRoot(root) {
// 1. 获取本次render的优先级, 初次构造返回 NoLanes.
const lanes = getNextLanes(root, NoLanes);
// 2. 从root节点开始, 至上而下更新.
const exitStatus = renderRootSync(root, lanes);
// 3. 将最新的 Fiber 树挂载到 root.finishedWork 节点上.
const finishedWork: Fiber = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 4. 进入 Commit 阶段.
commitRoot(root);
}
function performConcurrentWorkOnRoot(root) {
const originalCallbackNode = root.callbackNode;
// 1. 刷新 pending 状态的 effects, 有可能某些 effect 会取消本次任务.
const didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
if (root.callbackNode !== originalCallbackNode) {
// 任务被取消, 退出调用.
return null;
} else {
// Current task was not canceled. Continue.
}
}
// 2. 获取本次渲染的优先级.
const lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
);
// 3. 构造 Fiber 树.
const exitStatus = renderRootConcurrent(root, lanes);
if (
includesSomeLane(
workInProgressRootIncludedLanes,
workInProgressRootUpdatedLanes
)
) {
// 如果在 render 过程中产生了新 update, 且新 update 的优先级与最初 render 的优先级有交集.
// 那么最初 render 无效, 丢弃最初 render 的结果, 等待下一次调度.
prepareFreshStack(root, NoLanes);
} else if (exitStatus !== RootIncomplete) {
// 4. 异常处理: 有可能fiber构造过程中出现异常.
if (exitStatus === RootError) {
processError();
}
const finishedWork = root.current.alternate; // Fiber
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 5. 输出: 渲染 Fiber树.
finishConcurrentRender(root, exitStatus, lanes);
}
// 退出前再次检测, 是否还有其他更新, 是否需要发起新调度.
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
// 渲染被阻断, 返回一个新的 performConcurrentWorkOnRoot 函数, 等待下一次调度.
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
function renderRootSync(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
// 如果 FiberRoot 变动, 或者 update.lane 变动, 都会刷新栈帧, 丢弃上一次渲染进度.
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 刷新栈帧.
prepareFreshStack(root, lanes);
}
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// 重置全局变量, 表明 render 结束.
executionContext = prevExecutionContext;
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher();
// 如果 FiberRoot 变动, 或者 update.lane变动, 都会刷新栈帧, 丢弃上一次渲染进度.
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
resetRenderTimer();
// 刷新栈帧.
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
const prevInteractions = pushInteractions(root);
do {
try {
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// 重置全局变量.
resetContextDependencies();
popDispatcher(prevDispatcher);
executionContext = prevExecutionContext;
// Check if the tree has completed.
if (workInProgress !== null) {
// Still work remaining.
return RootIncomplete;
} else {
// Completed the tree.
// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
// Return the final exit status.
return workInProgressRootExitStatus;
}
}
function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
// 重置 FiberRoot 上的属性.
root.finishedWork = null;
root.finishedLanes = NoLanes;
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
cancelTimeout(timeoutHandle);
}
if (workInProgress !== null) {
let interruptedWork = workInProgress.return;
while (interruptedWork !== null) {
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
// 重置全局变量.
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null); // currentHostRootFiber.alternate.
workInProgressRootRenderLanes =
subtreeRenderLanes =
workInProgressRootIncludedLanes =
lanes;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield.
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork: Fiber): void {
// unitOfWork 就是被传入的 workInProgress.
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 如果没有派生出新的下级节点, 则进入 completeWork 阶段, 传入的是当前 unitOfWork.
completeUnitOfWork(unitOfWork);
} else {
// 如果派生出新的下级节点, 则递归处理.
workInProgress = next;
}
}
function _performUnitOfWork_Recursive(unitOfWork: Fiber): void {
beginWork(unitOfWork.alternate, unitOfWork, subtreeRenderLanes);
if (unitOfWork.child) _performUnitOfWork_Recursive(unitOfWork.child);
completeUnitOfWork(unitOfWork);
if (unitOfWork.sibling) _performUnitOfWork_Recursive(unitOfWork.sibling);
}
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
// 1. 设置 workInProgress 优先级为 NoLanes (最高优先级).
const updateLanes = workInProgress.lanes;
didReceiveUpdate = false;
workInProgress.lanes = NoLanes;
// 2. 根据 workInProgress 节点的类型, 用不同的方法派生出子节点.
switch (workInProgress.tag) {
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);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
}
}
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork;
// 外层循环控制并移动指针 (workInProgress/completedWork).
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
if ((completedWork.flags & Incomplete) === NoFlags) {
// 1. 处理 Fiber 节点, 会调用渲染器 (关联 Fiber 节点和 DOM 对象, 绑定事件等).
const next = completeWork(current, completedWork, subtreeRenderLanes);
if (next !== null) {
// 如果派生出其他的子节点, 则回到 beginWork 阶段进行处理.
workInProgress = next;
return;
}
// 重置子节点的优先级.
resetChildLanes(completedWork);
if (
returnFiber !== null &&
(returnFiber.flags & Incomplete) === NoFlags
) {
// 2. 收集当前 Fiber 节点以及其子树的副作用 Effects.
// 2.1 把子节点的副作用队列添加到父节点上.
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;
}
// 2.2 如果当前 Fiber 节点有副作用, 将其添加到子节点的副作用队列之后.
const flags = completedWork.flags;
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 如果有兄弟节点, 返回之后再次进入 beginWork 阶段.
workInProgress = siblingFiber;
return;
}
// 移动指针, 指向下一个节点.
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
// 已回溯到根节点, 设置 workInProgressRootExitStatus = RootCompleted.
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostRoot: {
const fiberRoot: FiberRoot = workInProgress.stateNode;
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
// 设置 fiber.flags.
workInProgress.flags |= Snapshot;
}
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
const currentHostContext = getHostContext();
// 1. 创建 DOM 对象.
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress
);
// 2. 把子树中的 DOM 对象 append 到本节点的 DOM 对象之后.
appendAllChildren(instance, workInProgress, false, false);
// 3. 设置 stateNode 属性, 指向 DOM 对象.
workInProgress.stateNode = instance;
if (
// 4. 设置DOM对象的属性, 绑定事件等.
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext
)
) {
// 设置 fiber.flags (Update).
markUpdate(workInProgress);
}
if (workInProgress.ref !== null) {
// 设置 fiber.flags (Ref).
markRef(workInProgress);
}
return null;
}
}
}
Host Root Fiber Rendering
function updateHostRoot(current, workInProgress, renderLanes) {
// 1. 状态计算, 更新整合到 workInProgress.memoizedState.
const updateQueue = workInProgress.updateQueue;
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState !== null ? prevState.element : null;
cloneUpdateQueue(current, workInProgress);
// 遍历 updateQueue.shared.pending, 提取有足够优先级的 update对象, 计算出最终的状态 workInProgress.memoizedState.
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState = workInProgress.memoizedState;
// 2. 获取下级 ReactElement 对象.
const nextChildren = nextState.element;
const root: FiberRoot = workInProgress.stateNode;
// 3. 根据 ReactElement 对象, 调用 reconcileChildren 生成 Fiber 子节点 (只生成次级子节点).
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
Host Component Fiber Rendering
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
) {
// 1. 状态计算, 由于 HostComponent 是无状态组件, 只需要收集 nextProps.
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
// 2. 获取下级 ReactElement 对象.
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// 如果子节点只有一个文本节点, 不用再创建一个 HostText 类型的 Fiber.
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// 设置 fiber.flags.
workInProgress.flags |= ContentReset;
}
// 设置 fiber.flags.
markRef(current, workInProgress);
// 3. 根据 ReactElement 对象, 调用 reconcileChildren 生成 Fiber 子节点(只生成次级子节点)
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
Class Component Fiber Rendering
Function Component Fiber Rendering
Reconciler Update Workflow
UpdateQueue
是一个循环队列.- 创建
Update
时机 (createUpdate
/enqueueUpdate
):ReactFiberReconciler.updateContainer
.ReactFiberClassComponent.setState
.ReactFiberHooks.dispatchAction
.
Reconciler.Render
阶段, 调用XXXClassInstance
/useXXX
, 遍历处理 Update Queue (processUpdateQueue
/HooksDispatcherOnUpdate
), 计算出 memoizedState, 利用 pendingProps 与 memoizedState 产生新的 ReactElement (ClassComponent.render()
/FunctionComponent()
).
interface Update<State> {
lane: Lane;
tag: 'UpdateState' | 'ReplaceState' | 'ForceUpdate' | 'CaptureUpdate';
payload: any;
callback: (() => mixed) | null;
next: Update<State> | null;
_eventTime: number;
}
interface SharedQueue<State> {
pending: Update<State> | null;
}
interface UpdateQueue<State> {
baseState: State;
firstBaseUpdate: Update<State> | null;
lastBaseUpdate: Update<State> | null;
shared: SharedQueue<State>;
effects: Array<Update<State>> | null; // Updates with `callback`.
}
ReactFiberClassComponent.setState:
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
// 1. 获取 ClassComponent 实例对应的 Fiber 节点.
const fiber = getInstance(inst);
// 2. 创建 Update 对象.
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
// 3. 将 Update 对象添加到当前 Fiber 节点的 updateQueue.
enqueueUpdate(fiber, update);
// 4. 请求调度, 进入 Reconciler.
scheduleUpdateOnFiber(fiber, lane, eventTime);
},
};
ReactFiberHooks.dispatchAction:
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A
) {
// 1. 创建 Update 对象.
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: null,
};
// 2. 将 Update 对象添加到当前 Hook 对象的 updateQueue.
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
// 3. 请求调度, 进入 Reconciler.
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
- createUpdate.
- enqueueUpdate.
- scheduleUpdateOnFiber.
- markUpdateLaneFromFiberToRoot:
找出 Fiber 树中受到本次
Update
影响的所有节点 (存在更新可能), 设置这些节点的fiber.lanes
或fiber.childLanes
. - ensureRootIsScheduled.
- flushSyncCallbacks.
- performSyncWorkOnRoot / performConcurrentWorkOnRoot.
- renderRootSync / renderRootConcurrent.
- workLoopSync / workLoopConcurrent.
- performUnitOfWork(workInProgress).
- beginWork:
- 若判断当前 Fiber 节点无需更新, 调用
bailoutOnAlreadyFinishedWork
循环检测子节点是否需要更新:instance.shouldComponentUpdate() === false
.workInProgress.pendingProps === current.memoizedProps
.hasLegacyContextChange() === false
.checkIfContextChanged(fiber.dependencies) === false
.includesSomeLane(fiber.lanes, renderLanes) === false
.
- 若判断当前 Fiber 节点需要更新, 调用
UpdateXXXComponent
进行更新.
- 若判断当前 Fiber 节点无需更新, 调用
- bailoutOnAlreadyFinishedWork:
- 若
includesSomeLane(renderLanes, workInProgress.childLanes) === false
表明子节点无需更新, 可直接进入回溯阶段 (completeUnitOfWork
). - 若
includesSomeLane(renderLanes, workInProgress.childLanes) === true
, 表明子节点需要更新, clone 并返回子节点.
- 若
- updateHostRoot/updateXXXComponent.
ReactClassComponent.render()
/ReactFunctionComponent()
/ReactDOMComponent.createElement()
: 遍历处理 Update Queue (processUpdateQueue
/HooksDispatcherOnUpdate
), 计算出 memoizedState, 利用 pendingProps 与 memoizedState 产生新的 ReactElement.- reconcileChildren:
- 通过 ReactElement 与 OldFiber, 产生或复用 ChildFiber.
- 设置
fiber.flags
, 标记副作用:Placement
/Deletion
/etc. - 对于
Deletion
Fiber, 在beginWork
阶段提前将其添加到父节点的 Effects 队列中 (该节点会脱离 Fiber 树, 不会再进入completeWork
阶段, 无法在此阶段收集此节点副作用).
- reconcileChildFibers.
- completeUnitOfWork: 收集副作用.
- completeWork: 收集副作用.
// 标记所有可能存在更新的节点, 并设置 fiber.lanes 与 fiber.childLanes.
function markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber, // 被更新的节点.
lane: Lane
): FiberRoot | null {
// 设置 sourceFiber.lanes.
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
let alternate = sourceFiber.alternate;
if (alternate !== null) {
// 同时设置 sourceFiber.alternate.lanes.
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
// 从 sourceFiber 开始, 向上遍历所有 Fiber, 直到 HostRootFiber.
// 设置沿途所有 fiber.childLanes 与 fiber.alternate.childLanes.
let node = sourceFiber;
let parent = sourceFiber.return;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
}
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root: FiberRoot = node.stateNode;
return root;
} else {
return null;
}
}
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
const updateLanes = workInProgress.lanes;
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)) {
// 当前渲染优先级 renderLanes 不包括 fiber.lanes, 表明当前 Fiber 节点无需更新.
didReceiveUpdate = false;
// 调用 bailoutOnAlreadyFinishedWork 循环检测子节点是否需要更新.
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// 当前节点需要更新.
workInProgress.lanes = NoLanes; // 最高优先级
switch (workInProgress.tag) {
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);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
}
}
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
// 渲染优先级不包括 workInProgress.childLanes, 表明子节点也无需更新.
// 返回 null, 直接进入回溯阶段.
return null;
} else {
// Fiber 自身无需更新, 但子节点需要更新, clone 并返回子节点.
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
}
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
// 非文本节点.
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode !== null) {
// 处理改动.
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
}
return null;
}
case HostText: {
// 文本节点.
const newText = newProps;
if (current !== null && workInProgress.stateNode !== null) {
const oldText = current.memoizedProps;
// 处理改动.
updateHostText(current, workInProgress, oldText, newText);
}
return null;
}
}
}
function updateHostComponent(
current: Fiber,
workInProgress: Fiber,
type: Type,
newProps: Props,
rootContainerInstance: Container
) {
const oldProps = current.memoizedProps;
if (oldProps === newProps) {
return;
}
const instance: Instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext
);
workInProgress.updateQueue = updatePayload;
// 如果有属性变动, 设置 fiber.flags |= Update, 等待 Commit 阶段处理.
if (updatePayload) {
markUpdate(workInProgress);
}
}
function updateHostText(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string
) {
// 如果有属性变动, 设置 fiber.flags |= Update, 等待 Commit 阶段处理.
if (oldText !== newText) {
markUpdate(workInProgress);
}
}
Reconciler Diff Workflow
Reconciler:
- O(n) incomplete tree comparison: only compare same level nodes.
ReactElement
+ Old Children Fiber -> New Children Fiber.- Create new children fiber (non exist/need update),
drop useless children fiber,
reuse old children fiber,
set
fiber.flags
:Placement
/Deletion
. prepare forCommit
stage. key
prop to hint for Fiber nodes reuse.- Detailed diff algorithm.
Different Types Elements
- Rebuild element and children.
Same Type DOM Elements
- Only update the changed attributes.
- Use
key
attribute to match children.
Best Practice
: give key
to <li>/<tr>/<tc>
elements
(stable, predictable, unique and not array indexed).
Same Type Component Elements
- Update the props to match the new element.
Reconcile Array Elements
- 第一次循环: 比较公共序列:
- 从左到右逐一遍历, 遇到一个无法复用的节点则退出循环.
- 第二次循环: 比较非公共序列
- 在第一次循环的基础上, 如果 oldFiber 队列遍历完成, 证明 newChildren 队列中剩余的对象全部都是新增.
- 此时继续遍历剩余的 newChildren 队列即可, 没有额外的 diff 比较.
- 在第一次循环的基础上, 如果 oldFiber 队列没有遍历完, 需要将 oldFiber 队列中剩余的对象都添加到一个 Map 集合中, 以 oldFiber.key 作为键.
- 此时继续遍历剩余的 newChildren 队列, 需要用 newChild.key 到 Map 集合中进行查找, 将匹配上的 oldFiber 取出与 newChild 进行 diff 比较.
- 清理工作:
- 在第二次循环结束后,
若 Map 集合中还有剩余的 oldFiber,
则说明 oldFiber 都是被删除的节点, 需要打上删除标记 (
Deletion
).
- 在第二次循环结束后,
若 Map 集合中还有剩余的 oldFiber,
则说明 oldFiber 都是被删除的节点, 需要打上删除标记 (
Reconciler Commit Workflow
Renderer and HostConfig Protocol
Renderer
:
- Implementing
HostConfig
protocol. - Rendering fiber tree to real contents:
- Web: DOM node.
- Native: native UI.
- Server: SSR strings.
- Real renderer demo.
HostConfig
protocol:
isPrimaryRender: true
.supportsHydration: true
: SSR renderer.supportsMutation: true
: React DOM renderer.supportsPersistence: true
: React Native renderer.- Platform timer functions:
- now.
- scheduleTimeout.
- cancelTimeout.
- Creation operations:
- createInstance.
- createTextInstance.
- UI tree operations:
- appendInitialChild.
- appendChild.
- appendChildToContainer.
- removeChildFromContainer.
- removeChild.
- clearContainer.
- Update props operations:
- finalizeInitialChildren.
- prepareUpdate.
- commitUpdate.
- commitTextUpdate.
- shouldSetTextContent.
- resetTextContent.
- Context and schedule operations:
- getRootHostContext.
- getChildHostContext.
- getPublicInstance.
- prepareForCommit.
- resetAfterCommit.
- preparePortalMount.
Commit Root
FiberRoot.finishedWork
:- 副作用队列挂载在根节点上 (
finishedWork.firstEffect
). - 最新 DOM 对象挂载在 HostComponent Fiber 上 (
fiber.stateNode
).
- 副作用队列挂载在根节点上 (
BeforeMutation
phase:- Read the state of the host tree right before DOM mutation.
- Process
Passive
/Snapshot
/Deletion
effects fiber. instance.getSnapshotBeforeUpdate
.
Mutation
phase.- Mutate the host tree, render UI.
- Process
ContentReset
/Ref
/Visibility
/Placement
/Update
/Deletion
/Hydrating
effects fiber.
Layout
phase.- After DOM mutation.
- Process
Update | Callback
effects fiber. instance.componentDidMount/componentDidUpdate
(synchronous).instance
callback forsetState
.useLayoutEffect
(synchronous).
CommitEffects
functions located in ReactFiberCommitWork.
function commitRoot(root: FiberRoot, recoverableErrors: null | Array<mixed>) {
const previousUpdateLanePriority = getCurrentUpdatePriority();
const prevTransition = ReactCurrentBatchConfig.transition;
try {
ReactCurrentBatchConfig.transition = null;
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root, recoverableErrors, previousUpdateLanePriority);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
return null;
}
function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<mixed>,
renderPriorityLevel: EventPriority
) {
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
flushRenderPhaseStrictModeWarningsInDEV();
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
if (finishedWork === null) {
return null;
}
// 清空 FiberRoot 对象上的属性.
root.finishedWork = null;
root.finishedLanes = NoLanes;
root.callbackNode = null;
root.callbackPriority = NoLane;
// Update the first and last pending times on this root.
// The new first pending time is whatever is left on the root fiber.
const remainingLanes = mergeLanes(
finishedWork.lanes,
finishedWork.childLanes
);
if (root === workInProgressRoot) {
// We can reset these now that they are finished.
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
}
// If there are pending passive effects, schedule a callback to process them.
// Do this as early as possible before anything else in commit phase.
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
// Check if there are any effects in the whole tree.
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {
// Store context.
const prevTransition = ReactCurrentBatchConfig.transition;
const previousPriority = getCurrentUpdatePriority();
const prevExecutionContext = executionContext;
ReactCurrentBatchConfig.transition = null;
setCurrentUpdatePriority(DiscreteEventPriority);
executionContext |= CommitContext;
// Reset this to null before calling life cycles.
ReactCurrentOwner.current = null;
// `BeforeMutation` phase:
// read the state of the host tree right before we mutate it.
// `getSnapshotBeforeUpdate` is called.
commitBeforeMutationEffects(root, finishedWork);
// `Mutation` phase:
// mutate the host tree.
commitMutationEffects(root, finishedWork, lanes);
resetAfterCommit(root.containerInfo);
// The workInProgress tree is now the current tree (during `componentDidMount`/`Update`).
root.current = finishedWork;
// `Layout` phase:
// `useLayoutEffect` is called.
commitLayoutEffects(finishedWork, root, lanes);
// Tell Scheduler to yield at the end of the frame,
// so the browser has an opportunity to paint.
requestPaint();
// Restore context.
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
} else {
// No effects.
root.current = finishedWork;
}
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
if (rootDoesHavePassiveEffects) {
// This commit has passive effects:
// Stash a reference to them.
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
} else {
// There were no passive effects:
// immediately release the cache pool for this render.
releaseRootPooledCache(root, remainingLanes);
}
// Always call this before exiting `commitRoot`,
// to ensure that any additional work on this root is scheduled.
ensureRootIsScheduled(root, now());
// If the passive effects are the result of a discrete render,
// flush them synchronously at the end of the current task
// so that the result is immediately observable.
if (
includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
root.tag !== LegacyRoot
) {
flushPassiveEffects();
}
// If layout work was scheduled, flush it now.
flushSyncCallbacks();
return null;
}
const BeforeMutationMask = Update | Snapshot | ChildDeletion | Visibility;
const MutationMask =
Placement |
Update |
ChildDeletion |
ContentReset |
Ref |
Hydrating |
Visibility;
const LayoutMask = Update | Callback | Ref | Visibility;
Before Mutation Phase
Passive
effects:FunctionComponent
fiber (hooks): If there are pending passive effects, schedule a callback (asynchronous) to process them, as early as possible before anything else in commit phase.useXXX
hooks normally run in asynchronous mode.useEffect
(asynchronous) run afteruseLayoutEffect
.
Snapshot
effects:HostRoot
fiber:HostConfig.clearContainer
.ClassComponent
fiber:instance.getSnapShotBeforeUpdate
.
Deletion
effects:commitBeforeMutationEffectsDeletion
->HostConfig.beforeActiveInstanceBlur
.
// `Passive` effects.
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
function flushPassiveEffects(): boolean {
// Returns whether passive effects were flushed.
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
const priorityLevel =
pendingPassiveEffectsRenderPriority > NormalSchedulerPriority
? NormalSchedulerPriority
: pendingPassiveEffectsRenderPriority;
pendingPassiveEffectsRenderPriority = NoSchedulerPriority;
return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
}
return false;
}
function flushPassiveEffectsImpl() {
if (rootWithPendingPassiveEffects === null) {
return false;
}
rootWithPendingPassiveEffects = null;
pendingPassiveEffectsLanes = NoLanes;
// 1. 执行 effect.destroy().
const unmountEffects = pendingPassiveHookEffectsUnmount;
pendingPassiveHookEffectsUnmount = [];
for (let i = 0; i < unmountEffects.length; i += 2) {
const effect = unmountEffects[i];
const fiber = unmountEffects[i + 1];
const destroy = effect.destroy;
effect.destroy = undefined;
if (typeof destroy === 'function') {
destroy();
}
}
// 2. 执行新 effect.create(), 重新赋值到 effect.destroy.
const mountEffects = pendingPassiveHookEffectsMount;
pendingPassiveHookEffectsMount = [];
for (let i = 0; i < mountEffects.length; i += 2) {
const effect = mountEffects[i];
const fiber = mountEffects[i + 1];
effect.destroy = create();
}
}
// `Snapshot` effects.
function commitBeforeMutationEffects(root: FiberRoot, firstChild: Fiber) {
HostConfig.prepareForCommit(root.containerInfo);
nextEffect = firstChild;
// DFS traverse.
while (nextEffect !== null) {
const fiber = nextEffect;
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
commitBeforeMutationEffectsDeletion(deletion);
}
}
const child = fiber.child;
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
// 1. Visit children.
nextEffect = child;
} else {
while (nextEffect !== null) {
const fiber = nextEffect;
commitBeforeMutationEffectsOnFiber(fiber);
const sibling = fiber.sibling;
// 2. Visit sibling.
if (sibling !== null) {
nextEffect = sibling;
break;
}
nextEffect = fiber.return;
}
}
}
}
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if ((flags & Snapshot) !== NoFlags) {
switch (finishedWork.tag) {
case ClassComponent: {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot: {
if (supportsMutation) {
const root = finishedWork.stateNode;
HostConfig.clearContainer(root.containerInfo);
}
break;
}
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types.
break;
default: {
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.'
);
}
}
}
}
function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
if (doesFiberContain(deletion, focusedInstanceHandle)) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(deletion);
}
}
Mutation Phase
ContentReset
effects:commitResetTextContent
->HostConfig.resetTextContext
.Ref
effects:commitAttachRef
/commitDetachRef
->HostConfig.getPublicInstance
.Visibility
effects:SuspenseComponent
fiber:markCommitTimeOfFallback
.OffscreenComponent
fiber:hideOrUnhideAllChildren
->HostConfig.hideInstance/hideTextInstance/unhideInstance/unhideTextInstance
.
Placement
effects:commitPlacement
->insertOrAppendPlacementNode
/insertOrAppendPlacementNodeIntoContainer
->HostConfig.appendChild/insertBefore/appendChildToContainer/insertInContainerBefore
.Update
effects:commitWork
->HostConfig.commitUpdate/commitTextUpdate/commitHydratedContainer/replaceContainerChildren
.Deletion
effects:commitDeletion
->HostConfig.removeChild/removeChildFromContainer/clearSuspenseBoundaryFromContainer
.Hydrating
effects.
export function commitMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes
) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = firstChild;
while (nextEffect !== null) {
const fiber = nextEffect;
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
commitDeletion(root, childToDelete, fiber);
}
}
const child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
// 1. Visit children.
nextEffect = child;
} else {
while (nextEffect !== null) {
const fiber = nextEffect;
commitMutationEffectsOnFiber(fiber, root, lanes);
const sibling = fiber.sibling;
// 2. Visit sibling.
if (sibling !== null) {
nextEffect = sibling;
break;
}
nextEffect = fiber.return;
}
}
}
inProgressLanes = null;
inProgressRoot = null;
}
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes
) {
const flags = finishedWork.flags;
if (flags & ContentReset) {
commitResetTextContent(finishedWork);
}
if (flags & Ref) {
const current = finishedWork.alternate;
if (current !== null) {
// 先清空 ref, 在第三阶段 (Layout), 再重新赋值.
commitDetachRef(current);
}
if (finishedWork.tag === ScopeComponent) {
commitAttachRef(finishedWork);
}
}
if (flags & Visibility) {
switch (finishedWork.tag) {
case SuspenseComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
if (isHidden) {
const current = finishedWork.alternate;
const wasHidden = current !== null && current.memoizedState !== null;
if (!wasHidden) {
markCommitTimeOfFallback();
}
}
break;
}
case OffscreenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
const current = finishedWork.alternate;
const wasHidden = current !== null && current.memoizedState !== null;
const offscreenBoundary: Fiber = finishedWork;
if (supportsMutation) {
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
}
break;
}
}
}
const primaryFlags = flags & (Placement | Update | Hydrating);
switch (primaryFlags) {
case Placement: {
// Placement
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement; // Clear bit.
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement; // Clear bit.
// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
case Hydrating: {
finishedWork.flags &= ~Hydrating; // Clear bit.
break;
}
case HydratingAndUpdate: {
finishedWork.flags &= ~Hydrating; // Clear bit.
// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
case Update: {
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
}
}
Layout Phase
Update | Callback
effects:instance.componentDidMount/componentDidUpdate
(synchronous).instance
callback forsetState
.useLayoutEffect
(synchronous).HostConfig.getPublicInstance/commitMount
.
function commitLayoutEffects(
finishedWork: Fiber,
root: FiberRoot,
committedLanes: Lanes
): void {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = finishedWork;
while (nextEffect !== null) {
const fiber = nextEffect;
const firstChild = fiber.child;
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
// 1. Visit children.
nextEffect = firstChild;
} else {
while (nextEffect !== null) {
const fiber = nextEffect;
if ((fiber.flags & LayoutMask) !== NoFlags) {
const current = fiber.alternate;
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
}
// Complete `commitLayoutEffects`.
if (fiber === subtreeRoot) {
nextEffect = null;
break;
}
const sibling = fiber.sibling;
// 2. Visit sibling.
if (sibling !== null) {
nextEffect = sibling;
break;
}
nextEffect = fiber.return;
}
}
}
inProgressLanes = null;
inProgressRoot = null;
}
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes
): void {
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
if (
!enableSuspenseLayoutEffectSemantics ||
!offscreenSubtreeWasHidden
) {
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
break;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if (!offscreenSubtreeWasHidden) {
if (current === null) {
instance.componentDidMount();
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(
finishedWork.type,
current.memoizedProps
);
const prevState = current.memoizedState;
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate
);
}
}
}
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
// 处理 update 回调函数, e.g: `this.setState({}, callback)`.
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostRoot: {
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
// 处理 update 回调函数, e.g: `this.setState({}, callback)`.
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if (current === null && finishedWork.flags & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
break;
}
case SuspenseComponent: {
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
break;
}
case HostText:
case HostPortal:
case Profiler:
case SuspenseListComponent:
case IncompleteClassComponent:
case ScopeComponent:
case OffscreenComponent:
case LegacyHiddenComponent: {
break;
}
default:
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.'
);
}
}
// 重新设置ref.
if (finishedWork.flags & Ref) {
commitAttachRef(finishedWork);
}
}
Reconciler Performance Tips
- Render: 通过一些启发式算法跳过没有发生变更的子树.
- Commit:
- 维护了一个列表用于记录变化的 Fiber, 不再访问其他 Fiber.
- 首次渲染 (Mount) 时只有
HostRootFiber.flags
会设置Placement
, 在 Commit 阶段只会执行一次插入操作.
- GC:
- Reuse
OldFiber
objects whenBailout
. current
Fiber tree andworkInProgress
Fiber tree forDouble Buffering
.
- Reuse
Minimal Reconciler Implementation
const performWork = deadline => {
if (!nextUnitOfWork) {
resetNextUnitOfWork();
}
// whether current status is idle status or not
while (nextUnitOfWork && deadline.timeRemaining() > ENOUGH_TIME) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
if (pendingCommit) {
commitAllWork(pendingCommit);
}
// checks if there's pending work
// if exist, performWork in **next frame** when idle
if (nextUnitOfWork || updateQueue.length > 0) {
requestIdleCallback(performWork);
}
};
const scheduleUpdate = (instance, partialState) => {
updateQueue.push({
from: CLASS_COMPONENT,
instance,
partialState,
});
requestIdleCallback(performWork);
};
// React.render function
const render = (elements, container) => {
updateQueue.push({
from: HOST_ROOT,
dom: container,
newProps: {
children: elements,
},
});
requestIdleCallback(performWork);
};