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 type { Container } from './types'
import Reconciler from './reconciler'
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 type { Container, OpaqueRoot } from './types'
import Reconciler from './reconciler'
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
// 请求回调:
function requestHostCallback(callback) {
// 1. 保存 callback.
scheduledHostCallback = callback
if (!isMessageLoopRunning) {
isMessageLoopRunning = true
// 2. 通过 MessageChannel 发送消息.
port.postMessage(null)
}
}
// 取消回调:
function cancelHostCallback() {
scheduledHostCallback = null
}
function requestHostTimeout(callback, ms) {
taskTimeoutID = setTimeout(() => {
callback(getCurrentTime())
}, ms)
}
function cancelHostTimeout() {
clearTimeout(taskTimeoutID)
taskTimeoutID = -1
}
// 是否让出主线程 (time slice):
function 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
}
}
// 请求绘制:
function requestPaint() {
needsPaint = true
}
// 实际回调函数处理:
function 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.
}
function 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
}
function 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
: 将渲染任务拆分为小的任务块并将任务分配到多个帧上的能力. - Ability to pause and resume rendering.
- Ability to prioritize updates.
- Ability to work on multiple tasks concurrently.
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
function 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)
}
function scheduleUpdate(instance, partialState) {
updateQueue.push({
from: CLASS_COMPONENT,
instance,
partialState,
})
requestIdleCallback(performWork)
}
// React.render function
function render(elements, container) {
updateQueue.push({
from: HOST_ROOT,
dom: container,
newProps: {
children: elements,
},
})
requestIdleCallback(performWork)
}