在 React 內部實現中,將 render 函數分為兩個階段:
- 渲染階段
- 提交階段
其中渲染階段可以分為 beginWork 和 completeWork 兩個階段,而提交階段對應著 commitWork。
在之前的root.render過程中,渲染過程無論是并發模式執行還是同步模式執行,都是執行到函數performUnitOfWork,所以,這次,具體看看performUnitOfWork函數的執行過程。
workLoopSync
performUnitOfWork
performUnitOfWork
函數的主要作用是執行單個 Fiber
節點的工作單元。該函數會根據當前 Fiber
節點的狀態,調用 beginWork
函數開始處理這個 Fiber
節點,并根據處理結果決定是繼續處理下一個 Fiber
節點,還是完成當前 Fiber
節點的工作。
函數參數含義
unitOfWork
:類型為Fiber
,表示當前要處理的Fiber
節點,也就是當前的工作單元。
function performUnitOfWork(unitOfWork: Fiber): void {// unitOfWork.alternate 指向當前 Fiber 節點的替代節點。在 React 的協調過程中,每個 Fiber 節點都有一個對應的替代節點,用于在雙緩沖機制中存儲舊的狀態。這里將其賦值給 current。const current = unitOfWork.alternate;// next:用于存儲 beginWork 函數返回的下一個要處理的 Fiber 節點。let next;// 調用 beginWork 函數開始處理當前 Fiber 節點,傳入當前 Fiber 節點的替代節點 current、當前 Fiber 節點 unitOfWork 以及相關的渲染車道 entangledRenderLanes。beginWork 函數會根據 Fiber 節點的類型和狀態,執行相應的工作,如創建新的 Fiber 節點、更新已有 Fiber 節點等,并返回下一個要處理的 Fiber 節點。next = beginWork(current, unitOfWork, entangledRenderLanes);// 將 unitOfWork 的 pendingProps(待處理的屬性)賦值給 memoizedProps(記憶化的屬性)。這意味著當前 Fiber 節點的屬性已經被處理并確定下來。unitOfWork.memoizedProps = unitOfWork.pendingProps;// 如果 next 為 null,說明 beginWork 函數沒有返回下一個要處理的 Fiber 節點,即當前 Fiber 節點的工作沒有產生新的工作。if (next === null) {// If this doesn't spawn new work, complete the current work.// 調用 completeUnitOfWork 函數完成當前 Fiber 節點的工作,可能會進行一些清理和提交操作。completeUnitOfWork(unitOfWork);} else {// 如果 next 不為 null,將 next 賦值給 workInProgress,表示下一個要處理的 Fiber 節點成為當前的工作進行中的節點,后續將繼續處理這個節點。workInProgress = next;}
}
渲染階段一beginWork
beginWork
函數的主要任務是根據不同的 Fiber
節點類型和更新情況,對當前 Fiber
節點進行處理,決定是復用舊的 Fiber
節點,還是創建新的子 Fiber
節點。該函數會根據 Fiber
節點的 tag
屬性(表示節點類型),調用不同的處理函數來更新或掛載節點。
beginWork
函數接收三個參數:
current
:舊的 Fiber 節點,若為null
則表示沒有舊節點。workInProgress
:新的 Fiber 節點,也就是要進行處理的節點。renderLanes
:渲染優先級車道,用于標識當前渲染任務的優先級。
function beginWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
): Fiber | null {// 有舊節點,非首次渲染if (current !== null) {// current.memoizedProps 存儲了舊 Fiber 節點上一次渲染時使用的屬性。const oldProps = current.memoizedProps;// workInProgress.pendingProps 存儲了新 Fiber 節點即將使用的屬性。const newProps = workInProgress.pendingProps;if (oldProps !== newProps ||hasLegacyContextChanged() )) {didReceiveUpdate = true;} else {const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current,renderLanes,);// 嘗試提前退出渲染流程if (!hasScheduledUpdateOrContext &&(workInProgress.flags & DidCapture) === NoFlags) {// No pending updates or context. Bail out now.didReceiveUpdate = false;return attemptEarlyBailoutIfNoScheduledUpdate(current,workInProgress,renderLanes,);}if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {didReceiveUpdate = true;} else {didReceiveUpdate = false;}}} else {// 首次渲染時,將 didReceiveUpdate 標記為 false。didReceiveUpdate = false;}// 將 workInProgress.lanes 設置為 NoLanes 時,意味著當前 Fiber 節點沒有需要處理的更新任務,或者更新任務已經處理完畢。// 會清除當前 workInProgress Fiber 節點上的所有更新標記。workInProgress.lanes = NoLanes;switch (workInProgress.tag) {// 省略代碼。。。case Throw: {// This represents a Component that threw in the reconciliation phase.// So we'll rethrow here. This might be a Thenable.throw workInProgress.pendingProps;}}}
?<Tag>FunctionComponent(常量為0)
負責解析函數組件的 props,并調用 updateFunctionComponent
執行組件的實際渲染。主要工作包括:
- 獲取組件類型和待處理的 props。
- 解析默認 props(針對非類組件)。
- 調用函數組件的更新邏輯。
case FunctionComponent: {// fiber的組件類型const Component = workInProgress.type;// 待處理propsconst unresolvedProps = workInProgress.pendingProps;const resolvedProps =disableDefaultPropsExceptForClasses ||workInProgress.elementType === Component? unresolvedProps: resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);return updateFunctionComponent(current,// 表示當前的 Fiber 節點workInProgress,// 正在處理的fiberComponent,// 函數組件resolvedProps,// 解析后的屬性propsrenderLanes,// 渲染優先級車道);
}
updateFunctionComponent
?
updateFunctionComponent
?函數負責執行函數組件,處理 Hooks,并根據返回的 JSX 內容生成新的子 Fiber 節點。該函數實現了函數組件的渲染邏輯、狀態管理以及性能優化(如 bailout 機制)。?
function updateFunctionComponent(current: null | Fiber,workInProgress: Fiber,Component: any,nextProps: any,renderLanes: Lanes,
) {let context;if (!disableLegacyContext && !disableLegacyContextForFunctionComponents) {// // 未屏蔽的上下文const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);// 經過屏蔽的上下文context = getMaskedContext(workInProgress, unmaskedContext);}let nextChildren;let hasId;prepareToReadContext(workInProgress, renderLanes);// 執行hooksnextChildren = renderWithHooks(current,workInProgress,Component,nextProps,context,renderLanes,);// 表示組件沒有接收到新的更新。if (current !== null && !didReceiveUpdate) {// bailoutHooks 會跳過 Hooks 的更新bailoutHooks(current, workInProgress, renderLanes);// 直接復用之前的結果。return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);}// React DevTools reads this flag.// 標記已完成workInProgress.flags |= PerformedWork;//reconcileChildren 函數用于對比新舊子節點,生成新的 Fiber 樹結構。reconcileChildren(current, workInProgress, nextChildren, renderLanes);return workInProgress.child;
}
工具函數之 resolveDefaultPropsOnNonClassComponent
resolveDefaultPropsOnNonClassComponent
是 React 中用于處理非類組件(如函數組件)默認 props 的函數。它會將組件定義的 defaultProps
對象與實際傳入的 props 合并,確保未顯式提供的 props 使用默認值。
函數參數含義:
Component
:組件本身(函數或類)。baseProps
:實際傳入的 props 對象。
function resolveDefaultPropsOnNonClassComponent(Component: any,baseProps: Object,
): Object {// 若啟用則僅對類組件保留默認 props 支持,函數組件將完全忽略 defaultProps。if (disableDefaultPropsExceptForClasses) {// Support for defaultProps is removed in React 19 for all types// except classes.return baseProps;}// 合并默認 propsif (Component && Component.defaultProps) {// Resolve default props. Taken from ReactElementconst props = assign({}, baseProps);const defaultProps = Component.defaultProps;for (const propName in defaultProps) {if (props[propName] === undefined) {props[propName] = defaultProps[propName];}}return props;}return baseProps;
}
工具函數之?bailoutHooks
bailoutHooks
?函數用于在組件無需重新渲染時跳過 Hooks 的更新。當組件的 props、context 或狀態沒有變化時,React 會觸發 bailout 邏輯,直接復用之前的渲染結果,從而避免不必要的計算和副作用執行。
function bailoutHooks(current: Fiber,workInProgress: Fiber,lanes: Lanes,
): void {// 復用 Hooks 更新隊列workInProgress.updateQueue = current.updateQueue;// 清除副作用// PassiveEffect:表示存在需要異步執行的副作用(如 useEffect)。
// UpdateEffect:表示存在需要同步執行的副作用(如 useLayoutEffect)。workInProgress.flags &= ~(PassiveEffect | UpdateEffect);// 從當前 Fiber 的 lanes 中移除本次渲染的優先級,表示該優先級的更新已處理完畢。current.lanes = removeLanes(current.lanes, lanes);
}
bailout 機制通常在以下情況觸發:
- props 未變化:前后 props 對象通過淺比較(
Object.is
)相等。 - context 未變化:組件依賴的上下文值沒有更新。
- 被 React.memo 包裹:函數組件被
React.memo
高階組件包裹,且 props 未變化。 - 無狀態更新:組件內部狀態沒有變化。
工具函數之?bailoutOnAlreadyFinishedWork
bailoutOnAlreadyFinishedWork
?函數用于在組件無需重新渲染時直接復用現有 Fiber 樹結構。當組件的 props、狀態或上下文沒有變化時,React 會觸發 bailout 邏輯,跳過該組件及其子樹的協調過程,直接復用之前的渲染結果,從而顯著提升性能。
function bailoutOnAlreadyFinishedWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
): Fiber | null {if (current !== null) {// 直接將當前 Fiber 的依賴信息復制到新 Fiber,避免重新收集依賴。workInProgress.dependencies = current.dependencies;}// 標記跳過的更新優先級markSkippedUpdateLanes(workInProgress.lanes);// Check if the children have any pending work.if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {// 啟用延遲上下文傳播的if (enableLazyContextPropagation && current !== null) {lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {return null;}} else {return null;}}// 克隆子節點cloneChildFibers(current, workInProgress);return workInProgress.child;
}
?工具函數之?markSkippedUpdateLanes
markSkippedUpdateLanes
?是 React 性能監控系統中的關鍵函數,用于記錄被跳過的更新優先級。當組件由于 bailout 機制(如 props 未變化)而跳過渲染時,React 使用該函數標記這些被跳過的優先級
function markSkippedUpdateLanes(lane: Lane | Lanes): void {workInProgressRootSkippedLanes = mergeLanes(lane,workInProgressRootSkippedLanes,);
}let workInProgressRootSkippedLanes: Lanes = NoLanes;
工具函數之?includesSomeLane
includesSomeLane
?函數用于判斷兩個優先級集合(Lanes
)是否存在交集。
function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane): boolean {return (a & b) !== NoLanes;
}
工具函數之 cloneChildFibers
cloneChildFibers
?函數用于在組件 bailout(跳過渲染)時淺克隆子 Fiber 節點。當父組件因 props 未變化而無需重新渲染時,React 會復用子 Fiber 結構,通過克隆操作創建新的 workInProgress 樹,避免重新協調整個子樹,從而顯著提升性能。
function cloneChildFibers(current: Fiber | null,workInProgress: Fiber,
): void {// 若 current 存在且子節點已變化,拋出錯誤if (current !== null && workInProgress.child !== current.child) {throw new Error('Resuming work not yet implemented.');}if (workInProgress.child === null) {return;}// 克隆首個子節點let currentChild = workInProgress.child;let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);workInProgress.child = newChild;newChild.return = workInProgress;// 循環克隆所有兄弟節點while (currentChild.sibling !== null) {currentChild = currentChild.sibling;newChild = newChild.sibling = createWorkInProgress(currentChild,currentChild.pendingProps,);newChild.return = workInProgress;}// 最后一個節點的 sibling 設為 null,確保鏈表正確結束。newChild.sibling = null;
}
工具函數之 prepareToReadContext
prepareToReadContext
函數是 React 渲染流程中的關鍵環節,用于為當前 Fiber 節點準備上下文讀取環境。它主要完成以下工作:
- 設置當前渲染的 Fiber 節點。
- 重置上下文依賴鏈表。
- 處理上下文更新標記,確保依賴的上下文變化能觸發組件重新渲染。
function prepareToReadContext(workInProgress: Fiber,renderLanes: Lanes,
): void {// currentlyRenderingFiber:全局變量,指向當前正在渲染的 Fiber 節點。
// lastContextDependency:全局變量,用于構建上下文依賴鏈表,初始化為 null。currentlyRenderingFiber = workInProgress;lastContextDependency = null;const dependencies = workInProgress.dependencies;if (dependencies !== null) {if (enableLazyContextPropagation) {// Reset the work-in-progress list// 重置依賴鏈表(延遲傳播模式)dependencies.firstContext = null;} else {const firstContext = dependencies.firstContext;if (firstContext !== null) {if (includesSomeLane(dependencies.lanes, renderLanes)) {// Context list has a pending update. Mark that this fiber performed work.// 上下文有更新,標記當前 Fiber 執行了工作markWorkInProgressReceivedUpdate();}// Reset the work-in-progress listdependencies.firstContext = null;}}}
}
?<Tag>HostRoot
case HostRoot:return updateHostRoot(current, workInProgress, renderLanes);
工具函數之 updateHostRoot
updateHostRoot
是 React 渲染流程中處理根組件(HostRoot
Fiber)的核心函數,負責協調根組件的更新邏輯,包括狀態處理、子節點 reconciliation、服務端渲染水合(Hydration)等。
function updateHostRoot(current: null | Fiber,workInProgress: Fiber,renderLanes: Lanes,
) {// 推入根組件的上下文,確保子組件能訪問正確的根狀態。// pushHostRootContext(workInProgress);if (current === null) {throw new Error('Should have a current fiber. This is a bug in React.');}// 獲取新 props 和舊狀態const nextProps = workInProgress.pendingProps;const prevState = workInProgress.memoizedState;const prevChildren = prevState.element;// 復制當前更新隊列到新的 workInProgress Fiber,確保更新操作的不可變性。cloneUpdateQueue(current, workInProgress);// 計算新的根狀態(nextState),處理狀態更新(如 setState)和副作用。processUpdateQueue(workInProgress, nextProps, null, renderLanes);const nextState: RootState = workInProgress.memoizedState;const root: FiberRoot = workInProgress.stateNode;// pushRootTransition(workInProgress, root, renderLanes);// suspendIfUpdateReadFromEntangledAsyncAction();const nextChildren = nextState.element;if (supportsHydration && prevState.isDehydrated) {} else {// resetHydrationState();// 若新子節點與舊子節點相同,通過 bailout 跳過調和過程,直接復用現有 Fiber。if (nextChildren === prevChildren) {return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);}// 當子節點變化時,通過 reconcileChildren 生成新的子 Fiber 樹,進入 diff 流程。reconcileChildren(current, workInProgress, nextChildren, renderLanes);}return workInProgress.child;
}
工具函數之 cloneUpdateQueue
cloneUpdateQueue
是 React 內部用于克隆更新隊列(Update Queue)的核心函數,主要作用是在調和(Reconciliation)階段為 workInProgress
Fiber 創建當前 Fiber(current
)的更新隊列副本。
function cloneUpdateQueue<State>(current: Fiber,workInProgress: Fiber,
): void {// Clone the update queue from current. Unless it's already a clone.const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);const currentQueue: UpdateQueue<State> = (current.updateQueue: any);// 僅當新舊隊列引用相同時才執行克隆(避免重復克隆)if (queue === currentQueue) {const clone: UpdateQueue<State> = {baseState: currentQueue.baseState,firstBaseUpdate: currentQueue.firstBaseUpdate,lastBaseUpdate: currentQueue.lastBaseUpdate,shared: currentQueue.shared,callbacks: null, // 重置回調,避免舊回調影響新 Fiber};workInProgress.updateQueue = clone;}
}
interface UpdateQueue<State> {baseState: State; // 基礎狀態(未處理更新的狀態)firstBaseUpdate: Update<State>; // 第一個未處理的更新lastBaseUpdate: Update<State>; // 最后一個未處理的更新shared: { [key: string]: mixed }; // 共享狀態(如上下文)callbacks: Callback | null; // 更新回調函數
}
工具函數之 processUpdateQueue
processUpdateQueue函數的核心作用是將組件的狀態更新隊列(queue
)中的更新合并到當前狀態(inst.state
),確保狀態按順序更新,并處理函數式更新和對象合并邏輯。
function processUpdateQueue(internalInstance: InternalInstance,inst: any,props: any,maskedLegacyContext: any,
): void {if (internalInstance.queue !== null && internalInstance.queue.length > 0) {// 處理更新隊列const oldQueue = internalInstance.queue;// oldReplace:表示是否用新狀態完全替換舊狀態(而非合并)。const oldReplace = internalInstance.replace;// 重置隊列為 null,避免重復處理internalInstance.queue = null;// 重置替換標志internalInstance.replace = false;// 僅一個更新項,直接替換狀態。if (oldReplace && oldQueue.length === 1) {inst.state = oldQueue[0];} else {// 初始化 nextStatelet nextState = oldReplace ? oldQueue[0] : inst.state;// 是否需要創建新對象(避免直接修改舊狀態)let dontMutate = true;// 遍歷更新隊列(從第一個有效更新開始)for (let i = oldReplace ? 1 : 0; i < oldQueue.length; i++) {// 獲取當前更新項const partial = oldQueue[i];const partialState =typeof partial === 'function'// 處理函數式更新(如 setState((state) => ({ ...state, count: state.count + 1 })))? partial.call(inst, nextState, props, maskedLegacyContext)// 非函數式更新直接使用值: partial;if (partialState != null) {if (dontMutate) {// 首次修改,創建新對象以保持不可變性dontMutate = false;nextState = assign({}, nextState, partialState);} else {// 后續修改直接合并(已創建新對象,可安全修改)assign(nextState, partialState);}}}// 最終狀態賦值inst.state = nextState;}} else {// 清空空隊列internalInstance.queue = null;}
}
?<Tag>HostHoistable
React 渲染流程中處理 可提升資源節點(HostHoistable)。當瀏覽器支持資源優化(supportsResources
為 true
)時,會調用 updateHostHoistable
函數對這類節點進行特殊處理,以優化關鍵資源(如 CSS、JavaScript)的加載順序和優先級,從而提升應用性能。
case HostHoistable:if (supportsResources) {return updateHostHoistable(current, workInProgress, renderLanes);}
updateHostHoistable
updateHostHoistable
是 React 渲染流程中處理 可提升資源節點(如 CSS、JS 等) 的核心函數。
優化關鍵資源的加載執行流程:
- 標記 ref 相關副作用
- 初始化或更新資源對象(通過
getResource
) - 管理資源狀態(存儲在
memoizedState
中) - 決定是否創建 DOM 實例(非資源節點或非水合模式下)
function updateHostHoistable(current: null | Fiber,workInProgress: Fiber,renderLanes: Lanes,
) {// 標記出來ref相關副作用markRef(current, workInProgress);// 首次渲染if (current === null) {// 通過 getResource 獲取資源對象(如 CSS、JS 資源)const resource = getResource(workInProgress.type,null,workInProgress.pendingProps,null,);if (resource) {// 將資源對象存儲在 memoizedState 中workInProgress.memoizedState = resource;} else {if (!getIsHydrating()) {// This is not a Resource Hoistable and we aren't hydrating so we construct the instance.// 創建非資源類型的可提升節點(如普通 DOM 元素)workInProgress.stateNode = createHoistableInstance(workInProgress.type,workInProgress.pendingProps,getRootHostContainer(),workInProgress,);}}} else {// Get Resource may or may not return a resource. either way we stash the result// on memoized state.// 對比新舊 props,決定是否需要更新資源// 復用或創建新資源對象,更新到 memoizedStateworkInProgress.memoizedState = getResource(workInProgress.type,current.memoizedProps,workInProgress.pendingProps,current.memoizedState,);}return null;
}
工具函數之 markRef
markRef
函數主要作用是標記 workInProgress
Fiber 是否需要執行 ref
相關的副作用(Effect)。
function markRef(current: Fiber | null, workInProgress: Fiber) {const ref = workInProgress.ref;if (ref === null) {if (current !== null && current.ref !== null) {// Schedule a Ref effectworkInProgress.flags |= Ref | RefStatic;}} else {if (typeof ref !== 'function' && typeof ref !== 'object') {throw new Error('Expected ref to be a function, an object returned by React.createRef(), or undefined/null.',);}if (current === null || current.ref !== ref) {// Schedule a Ref effectworkInProgress.flags |= Ref | RefStatic;}}
}
createHoistableInstance
創建可提升實例?
function createHoistableInstance(type: string,props: Props,rootContainerInstance: Container,internalInstanceHandle: Object,
): Instance {//n獲取document對象const ownerDocument = getOwnerDocumentFromRootContainer(rootContainerInstance,);// 創建elementconst domElement: Instance = ownerDocument.createElement(type);// 用于建立 DOM 節點與 Fiber 節點雙向映射precacheFiberNode(internalInstanceHandle, domElement);// 將 JSX 中的 props 信息存儲到對應 DOM 節點上updateFiberProps(domElement, props);setInitialProperties(domElement, type, props);// 標記 DOM 節點為 可提升(Hoistable) markNodeAsHoistable(domElement);return domElement;
}
工具函數之 precacheFiberNode
precacheFiberNode
是 React 內部用于建立 DOM 節點與 Fiber 節點雙向映射的核心函數。
function precacheFiberNode(hostInst: Fiber,node: Instance | TextInstance | SuspenseInstance | ReactScopeInstance,
): void {(node: any)[internalInstanceKey] = hostInst;
}const internalInstanceKey = '__reactFiber$' + randomKey;
工具函數之 updateFiberProps
updateFiberProps
是 React 內部用于將 JSX 中的 props 信息存儲到對應 DOM 節點上的工具函數。
function updateFiberProps(node: Instance | TextInstance | SuspenseInstance,props: Props,
): void {(node: any)[internalPropsKey] = props;
}const internalPropsKey = '__reactProps$' + randomKey;
工具函數之 markNodeAsHoistable
markNodeAsHoistable
是 React 內部用于標記 DOM 節點為 可提升(Hoistable) 的工具函數。
function markNodeAsHoistable(node: Node) {(node: any)[internalHoistableMarker] = true;
}const internalHoistableMarker = '__reactMarker$' + randomKey;
getResource
getResource
是 React 內部用于管理和復用關鍵資源(如 CSS、JavaScript 文件)的核心函數。它通過資源根節點(resourceRoot
)追蹤已加載的資源,實現資源共享、預加載和避免重復加載,從而優化應用性能。
function getResource(type: string,currentProps: any,pendingProps: any,currentResource: null | Resource,
): null | Resource {// 獲取資源根節點const resourceRoot = getCurrentResourceRoot();if (!resourceRoot) {throw new Error('"resourceRoot" was expected to exist. This is a bug in React.',);}switch (type) {case 'meta':case 'title': {return null;}// 處理資源類型stylecase 'style': {if (// precedence:樣式優先級(如 'high'、'low'),用于控制加載順序。typeof pendingProps.precedence === 'string' &&// href:外部樣式表的 URL。typeof pendingProps.href === 'string') {// 資源鍵生成。getStyleKey:生成 URL 的唯一哈希值,確保相同 URL 的資源被復用。const key = getStyleKey(pendingProps.href);// 查找資源。hoistableStyles:Map 對象,鍵為資源 URL 哈希值,值為資源對象。const styles = getResourcesFromRoot(resourceRoot).hoistableStyles;let resource = styles.get(key);if (!resource) {// 資源對象結構resource = {type: 'style', // 資源類型instance: null, // DOM 實例(加載后填充)count: 0, // 引用計數(用于垃圾回收)state: null, // 資源狀態(如加載中、已完成)};// 將新資源存入緩存styles.set(key, resource);}return resource;}// 無效資源處理return {type: 'void', // 特殊類型,表示無效資源instance: null,count: 0,state: null,};}// 處理資源類型linkcase 'link': {if (// rel="stylesheet":確認是樣式表資源pendingProps.rel === 'stylesheet' &&// href 和 precedence 存在且類型正確typeof pendingProps.href === 'string' &&typeof pendingProps.precedence === 'string') {const qualifiedProps: StylesheetQualifyingProps = pendingProps;// 生成唯一keyconst key = getStyleKey(qualifiedProps.href);// 查找資源const styles = getResourcesFromRoot(resourceRoot).hoistableStyles;let resource = styles.get(key);if (!resource) {const ownerDocument = getDocumentFromRoot(resourceRoot);// 定義資源結構resource = ({type: 'stylesheet',instance: null, // 對應 DOM 元素count: 0, // 引用計數state: {loading: NotLoaded, // 加載狀態preload: null, // 預加載 Promise},}: StylesheetResource);// 存入緩存styles.set(key, resource);// 通過 CSS 選擇器查找已存在的 <link> 標簽const instance = ownerDocument.querySelector(getStylesheetSelectorFromKey(key),);if (instance) {// 使用內部屬性 _p 判斷加載狀態const loadingState: ?Promise<mixed> = (instance: any)._p;if (loadingState) {// This instance is inserted as part of a boundary reveal and is not yet// loaded// 正在加載中} else {// 實例已經加載完成// This instance is already loadedresource.instance = instance;resource.state.loading = Loaded | Inserted;}}// 預加載機制流程:// 生成預加載配置(如 as="style")// 創建 <link rel="preload"> 標簽// 監聽加載完成事件,更新資源狀態if (!preloadPropsMap.has(key)) {const preloadProps = preloadPropsFromStylesheet(qualifiedProps);preloadPropsMap.set(key, preloadProps);if (!instance) {preloadStylesheet(ownerDocument,key,preloadProps,resource.state,);}}}// if (currentProps && currentResource === null) {// let diff = '';// }return resource;} else {// if (currentProps && currentResource !== null) {// let diff = '';// }return null;}}case 'script': {const async = pendingProps.async;const src = pendingProps.src;if (typeof src === 'string' &&async &&typeof async !== 'function' &&typeof async !== 'symbol') {const key = getScriptKey(src);const scripts = getResourcesFromRoot(resourceRoot).hoistableScripts;let resource = scripts.get(key);if (!resource) {resource = {type: 'script',instance: null,count: 0,state: null,};scripts.set(key, resource);}return resource;}return {type: 'void',instance: null,count: 0,state: null,};}default: {throw new Error(`getResource encountered a type it did not expect: "${type}". this is a bug in React.`,);}}
}
1、處理style類型的資源
處理 外部樣式表資源(<style>
標簽) 的邏輯分支。它通過 precedence
和 href
屬性識別需要優化加載的樣式資源,利用緩存機制避免重復加載,并返回標準化的資源對象。
2、處理link類型的資源
React 內部處理 <link rel="stylesheet">
資源的核心邏輯,主要負責:
- 資源去重:通過 URL 緩存避免重復加載同一 CSS 文件
- 狀態管理:跟蹤樣式表的加載狀態(未加載、加載中、已完成)
- 預加載優化:使用
<link rel="preload">
加速關鍵樣式的加載 - 服務端渲染集成:支持與 SSR 流程配合,提前收集關鍵 CSS
3、處理script類型的資源
?
工具函數之 preloadPropsFromStylesheet
preloadPropsFromStylesheet
是 React 內部用于將 <link rel="stylesheet">
轉換為 <link rel="preload">
的工具函數。
function preloadPropsFromStylesheet(props: StylesheetQualifyingProps,
): PreloadProps {return {rel: 'preload', // 將 rel 從 'stylesheet' 改為 'preload'as: 'style', // 指定資源類型為樣式表href: props.href, // 保留原始 URLcrossOrigin: props.crossOrigin, // 跨域配置integrity: props.integrity, // 內容完整性校驗media: props.media, // 媒體查詢條件hrefLang: props.hrefLang, // 語言設置referrerPolicy: props.referrerPolicy, // 引用策略};
}
// 原始 JSX
<link rel="stylesheet" href="critical.css" precedence="high" />// React 處理后
<link rel="preload" as="style" href="critical.css" /> // 預加載階段
<link rel="stylesheet" href="critical.css" /> // 正式應用階段
工具函數之 preloadStylesheet
preloadStylesheet
是 React 內部用于預加載 CSS 資源的核心函數,通過創建 <link rel="preload">
標簽實現樣式表的并行加載,避免阻塞主線程渲染。
function preloadStylesheet(ownerDocument: Document,key: string,preloadProps: PreloadProps,state: StylesheetState,
) {// 檢查已有預加載標簽const preloadEl = ownerDocument.querySelector(getPreloadStylesheetSelectorFromKey(key),);if (preloadEl) {// If we find a preload already it was SSR'd and we won't have an actual// loading state to track. For now we will just assume it is loaded// 服務端渲染已插入預加載標簽,直接標記為已加載state.loading = Loaded;} else {// 創建預加載標簽const instance = ownerDocument.createElement('link');state.preload = instance;// 監聽加載事件instance.addEventListener('load', () => (state.loading |= Loaded));instance.addEventListener('error', () => (state.loading |= Errored));// 設置標簽屬性并插入文檔setInitialProperties(instance, 'link', preloadProps);markNodeAsHoistable(instance); // 標記為可提升資源(ownerDocument.head: any).appendChild(instance); // 插入到文檔頭部}
}
function getPreloadStylesheetSelectorFromKey(key: string) {return `link[rel="preload"][as="style"][${key}]`;
}
工具函數之 getCurrentResourceRoot
getCurrentResourceRoot()
是 React 內部用于獲取當前渲染環境中 可提升資源根節點(HoistableRoot) 的工具函數。
function getCurrentResourceRoot(): null | HoistableRoot {// 獲取當前根容器const currentContainer = getCurrentRootHostContainer();// getHoistableRoot提取可提升資源根節點return currentContainer ? getHoistableRoot(currentContainer) : null;
}
function getCurrentRootHostContainer(): null | Container {return rootInstanceStackCursor.current;
}
function createCursor<T>(defaultValue: T): StackCursor<T> {return {current: defaultValue,};
}
?工具函數之 getHoistableRoot
function getHoistableRoot(container: Container): HoistableRoot {return typeof container.getRootNode === 'function'// Node 接口的 getRootNode() 方法返回上下文中的根節點? container.getRootNode()// Node.ownerDocument 只讀屬性會返回當前節點的頂層的 document 對象: container.ownerDocument;
}
工具函數之 getResourcesFromRoot?
function getResourcesFromRoot(root: HoistableRoot): RootResources {// 獲取節點上的設置的資源let resources = (root: any)[internalRootNodeResourcesKey];if (!resources) {// 設置resources = (root: any)[internalRootNodeResourcesKey] = {hoistableStyles: new Map(),hoistableScripts: new Map(),};}return resources;
}const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
?<Tag>HostSingleton
React 渲染流程中處理 單節點宿主組件。
// Fall through
case HostSingleton:if (supportsSingletons) {return updateHostSingleton(current, workInProgress, renderLanes);}
updateHostSingleton
updateHostSingleton
是 React 渲染流程中處理 單節點宿主組件(如 <div>
、<span>
等 DOM 元素) 的核心函數。
function updateHostSingleton(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
) {// 將當前 Fiber 節點的上下文(如命名空間、文檔模式)壓入棧中pushHostContext(workInProgress);if (current === null) {// 服務端渲染水合處理// claimHydratableSingleton(workInProgress);}const nextChildren = workInProgress.pendingProps.children;// 首次渲染if (current === null && !getIsHydrating()) {// 使用 reconcileChildFibers 創建新的子 Fiber 樹workInProgress.child = reconcileChildFibers(workInProgress,null,nextChildren,renderLanes,);} else {// 更新// 使用 reconcileChildren 對比新舊子節點,執行最小化 DOM 操作reconcileChildren(current, workInProgress, nextChildren, renderLanes);}// 標記ref副作用markRef(current, workInProgress);// 返回子 Fiber 節點return workInProgress.child;
}
?<Tag>HostComponent?
React 渲染流程中處理 宿主組件(如 HTML 標簽)。
// Fall through
case HostComponent:return updateHostComponent(current, workInProgress, renderLanes);
工具函數之 updateHostComponent
updateHostComponent
是 React 渲染流程中處理 宿主組件(如 HTML 標簽) 的核心函數。
function updateHostComponent(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
) {if (current === null) {// tryToClaimNextHydratableInstance(workInProgress);}// 設置當前渲染上下文pushHostContext(workInProgress);const type = workInProgress.type;const nextProps = workInProgress.pendingProps;const prevProps = current !== null ? current.memoizedProps : null;let nextChildren = nextProps.children;const isDirectTextChild = shouldSetTextContent(type, nextProps);// 對于純文本子節點(如 <div>Hello</div>),直接設置 textContent// 避免創建額外的文本節點,提升渲染效率if (isDirectTextChild) {nextChildren = null;} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {// If we're switching from a direct text child to a normal child, or to// empty, we need to schedule the text content to be reset.workInProgress.flags |= ContentReset;}// 異步狀態處理(并發模式)if (enableAsyncActions) {const memoizedState = workInProgress.memoizedState;if (memoizedState !== null) {// renderTransitionAwareHostComponentWithHooks 處理異步更新const newState = renderTransitionAwareHostComponentWithHooks(current,workInProgress,renderLanes,);// 根據渲染器類型(主渲染器或輔助渲染器)更新上下文值if (isPrimaryRenderer) {// _currentValue 存儲主渲染器的上下文值HostTransitionContext._currentValue = newState;} else {// _currentValue2 存儲輔助渲染器的上下文值HostTransitionContext._currentValue2 = newState;}// 懶傳播if (enableLazyContextPropagation) {// In the lazy propagation implementation, we don't scan for matching// consumers until something bails out.} else {if (didReceiveUpdate) {if (current !== null) {const oldStateHook: Hook = current.memoizedState;const oldState: TransitionStatus = oldStateHook.memoizedState;if (oldState !== newState) {// 更新上下文并傳播變化propagateContextChange(workInProgress,HostTransitionContext,renderLanes,);}}}}}}markRef(current, workInProgress);// 協調子節點reconcileChildren(current, workInProgress, nextChildren, renderLanes);return workInProgress.child;
}
renderTransitionAwareHostComponentWithHooks?
處理hook鉤子函數。
function renderTransitionAwareHostComponentWithHooks(current: Fiber | null,workInProgress: Fiber,lanes: Lanes,
): TransitionStatus {if (!enableAsyncActions) {throw new Error('Not implemented.');}return renderWithHooks(current,workInProgress,TransitionAwareHostComponent,null,null,lanes,);
}
工具函數之 propagateContextChange?
function propagateContextChange<T>(workInProgress: Fiber,context: ReactContext<T>,renderLanes: Lanes,
): void {if (enableLazyContextPropagation) {const forcePropagateEntireTree = true;propagateContextChanges(workInProgress,[context],renderLanes,forcePropagateEntireTree,);} else {propagateContextChange_eager(workInProgress, context, renderLanes);}
}
工具函數之 propagateContextChange_eager
propagateContextChange_eager
是 React 中用于 主動傳播上下文變化 的核心函數(適用于非懶傳播模式)。當上下文(Context
)的值發生改變時,該函數會遍歷相關 Fiber 樹,找到依賴該上下文的組件并為其標記更新,確保組件能響應上下文變化并重新渲染。
function propagateContextChange_eager<T>(workInProgress: Fiber,context: ReactContext<T>,renderLanes: Lanes,
): void {// Only used by eager implementationif (enableLazyContextPropagation) {return;}// Fiber 樹遍歷初始化let fiber = workInProgress.child;if (fiber !== null) {// Set the return pointer of the child to the work-in-progress fiber.// 設置子節點的 return 指針指向父節點fiber.return = workInProgress;}// 深度優先遍歷循環while (fiber !== null) {let nextFiber;// Visit this fiber.// 獲取當前節點依賴的上下文列表const list = fiber.dependencies;if (list !== null) {nextFiber = fiber.child;let dependency = list.firstContext;while (dependency !== null) {// Check if the context matches.// 匹配到目標上下文,觸發更新邏輯if (dependency.context === context) {// Match! Schedule an update on this fiber.// 為類組件生成強制更新if (fiber.tag === ClassComponent) {// Schedule a force update on the work-in-progress.const lane = pickArbitraryLane(renderLanes);const update = createUpdate(lane);update.tag = ForceUpdate;// Inlined `enqueueUpdate` to remove interleaved update check// 入隊更新(直接操作隊列,避免中間檢查)const updateQueue = fiber.updateQueue;if (updateQueue === null) {// Only occurs if the fiber has been unmounted.} else {const sharedQueue: SharedQueue<any> = (updateQueue: any).shared;const pending = sharedQueue.pending;// update是是一個循環鏈表結構if (pending === null) {// This is the first update. Create a circular list.update.next = update;} else {update.next = pending.next;pending.next = update;}sharedQueue.pending = update;}}// 標記當前節點及其 alternate 節點的更新優先級fiber.lanes = mergeLanes(fiber.lanes, renderLanes);const alternate = fiber.alternate;if (alternate !== null) {// 合并車道alternate.lanes = mergeLanes(alternate.lanes, renderLanes);}// 向上傳播任務到父路徑scheduleContextWorkOnParentPath(fiber.return,renderLanes,workInProgress,);// Mark the updated lanes on the list, too.list.lanes = mergeLanes(list.lanes, renderLanes);// Since we already found a match, we can stop traversing the// dependency list.// 找到一個匹配后,無需繼續遍歷依賴列表break;}dependency = dependency.next;}} else if (fiber.tag === ContextProvider) {// Don't scan deeper if this is a matching provider// 若當前節點是匹配的 Provider,停止向下遍歷(避免重復處理)nextFiber = fiber.type === workInProgress.type ? null : fiber.child;} else if (fiber.tag === DehydratedFragment) {// 處理 Suspense 邊界的脫水片段,標記其父 Suspense 節點const parentSuspense = fiber.return;if (parentSuspense === null) {throw new Error('We just came from a parent so we must have had a parent. This is a bug in React.',);}parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);const alternate = parentSuspense.alternate;if (alternate !== null) {alternate.lanes = mergeLanes(alternate.lanes, renderLanes);}scheduleContextWorkOnParentPath(parentSuspense,renderLanes,workInProgress,);// 跳過子樹,直接處理兄弟節點nextFiber = fiber.sibling;} else {// Traverse down.nextFiber = fiber.child;}if (nextFiber !== null) {// Set the return pointer of the child to the work-in-progress fiber.nextFiber.return = fiber;} else {// No child. Traverse to next sibling.nextFiber = fiber;while (nextFiber !== null) {if (nextFiber === workInProgress) {// We're back to the root of this subtree. Exit.nextFiber = null;break;}const sibling = nextFiber.sibling;if (sibling !== null) {// Set the return pointer of the sibling to the work-in-progress fiber.sibling.return = nextFiber.return;nextFiber = sibling;break;}// No more siblings. Traverse up.nextFiber = nextFiber.return;}}fiber = nextFiber;}
}
工具函數之 scheduleContextWorkOnParentPath
scheduleContextWorkOnParentPath
是 React 中用于 向上傳播上下文更新任務 的核心函數
function scheduleContextWorkOnParentPath(parent: Fiber | null,renderLanes: Lanes,propagationRoot: Fiber,
) {// Update the child lanes of all the ancestors, including the alternates.let node = parent;// 從當前父節點出發遍歷while (node !== null) {// 獲取備用 Fiber 節點(雙緩沖機制)const alternate = node.alternate;// 檢查當前節點的 childLanes 是否包含新的 renderLanesif (!isSubsetOfLanes(node.childLanes, renderLanes)) {// 合并車道node.childLanes = mergeLanes(node.childLanes, renderLanes);if (alternate !== null) {// 同步更新備用節點alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);}} else if (alternate !== null &&!isSubsetOfLanes(alternate.childLanes, renderLanes)) {alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);}// 到達傳播根節點,停止遍歷if (node === propagationRoot) {break;}// 向上處理父節點node = node.return;}
}
?<Tag>HostText?
case HostText:return updateHostText(current, workInProgress);
updateHostText?
function updateHostText(current: null | Fiber, workInProgress: Fiber) {// 說明首次渲染或沒有可對比的舊fiberif (current === null) {//tryToClaimNextHydratableTextInstance(workInProgress);}return null;
}
?<Tag>HostPortal?
case HostPortal:return updatePortalComponent(current, workInProgress, renderLanes);
updatePortalComponent?
function updatePortalComponent(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
) {pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);const nextChildren = workInProgress.pendingProps;if (current === null) {workInProgress.child = reconcileChildFibers(workInProgress,null,nextChildren,renderLanes,);} else {reconcileChildren(current, workInProgress, nextChildren, renderLanes);}return workInProgress.child;
}
渲染階段二completeUnitOfWork
completeUnitOfWork
函數在 React 的協調過程中負責完成單個 Fiber
節點的工作單元。它會不斷嘗試完成當前 Fiber
節點的工作,處理可能出現的未完成情況,根據處理結果決定是繼續處理兄弟節點還是返回父節點,直到完成整個 Fiber
樹的工作,并在到達根節點時更新根節點的退出狀態。
執行過程:
- 初始化當前要處理的 filbert 節點,定義為
completedWork。
- do...while 循環,直到completedWork 為 null。
- 如果 fiber 是否存在未完成的工作,調用 unwindUnitOfWork 函數處理未完成的工作,然后跳出 do...while 循環。
- 沒有未完成的工作,則調用 completeWork 完成當前節點的工作。
function completeUnitOfWork(unitOfWork: Fiber): void {// completedWork 初始化為傳入的 unitOfWorklet completedWork: Fiber = unitOfWork;// 使用 do-while 循環來處理 Fiber 節點的工作do {// 檢查 completedWork 的 flags 屬性中是否包含 Incomplete 標志。如果包含,說明當前 Fiber 節點的工作未完成。if ((completedWork.flags & Incomplete) !== NoFlags) {const skipSiblings = workInProgressRootDidSkipSuspendedSiblings;// 用 unwindUnitOfWork 函數處理未完成的工作unwindUnitOfWork(completedWork, skipSiblings);return;}// 獲取當前 Fiber 節點的替代節點,用于存儲舊的狀態。const current = completedWork.alternate;// 獲取當前 Fiber 節點的父節點,用于在完成當前節點工作后返回父節點繼續處理。const returnFiber = completedWork.return;// 用于存儲 completeWork 函數返回的下一個要處理的 Fiber 節點。let next;// 調用 startProfilerTimer 函數開始記錄性能分析時間。// startProfilerTimer(completedWork);// 調用 completeWork 函數完成當前 Fiber 節點的工作,傳入當前 Fiber 節點的替代節點 current、當前 Fiber 節點 completedWork 以及相關的渲染車道 entangledRenderLanes。next = completeWork(current, completedWork, entangledRenderLanes);// 如果 next 不為 null,說明完成當前 Fiber 節點的工作產生了新的工作。if (next !== null) {// Completing this fiber spawned new work. Work on that next.// 將 next 賦值給 workInProgress,表示下一個要處理的 Fiber 節點成為當前的工作進行中的節點,函數直接返回。workInProgress = next;return;}// 獲取當前 Fiber 節點的兄弟節點 siblingFiber。const siblingFiber = completedWork.sibling;if (siblingFiber !== null) {// If there is more work to do in this returnFiber, do that next.// 如果兄弟節點不為 null,將兄弟節點賦值給 workInProgress,表示接下來要處理兄弟節點的工作,函數直接返回。workInProgress = siblingFiber;return;}// 如果沒有新產生的工作,也沒有兄弟節點,將 completedWork 更新為其父節點 returnFiber,同時更新 workInProgress 為父節點,繼續處理父節點的工作。completedWork = returnFiber;// Update the next thing we're working on in case something throws.workInProgress = completedWork;// 只要 completedWork 不為 null,就會繼續循環處理。} while (completedWork !== null);// We've reached the root.// 如果根節點的退出狀態為 RootInProgress(正在進行中),將其更新為 RootCompleted(已完成)。if (workInProgressRootExitStatus === RootInProgress) {workInProgressRootExitStatus = RootCompleted;}
}
completeWork
completeWork
函數的主要職責是根據當前 Fiber 節點的類型(通過 workInProgress.tag
標識),對該節點進行相應的處理,比如更新 DOM、設置屬性等。(負責單個fiber節點的處理)
function completeWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
): Fiber | null {const newProps = workInProgress.pendingProps;popTreeContext(workInProgress);switch (workInProgress.tag) {// 省略的代碼case Throw: {if (!disableLegacyMode) {// Only Legacy Mode completes an errored node.return null;}}}throw new Error(`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +'React. Please file an issue.',);
}
?<Tag>FunctionComponent/LazyComponent/ForwardRef/...
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:bubbleProperties(workInProgress);return null;
bubbleProperties
bubbleProperties
函數的主要功能是從一個已完成工作的 Fiber
節點(completedWork
)的子節點中收集信息,將這些信息冒泡到該節點上。這些信息主要包括子節點的渲染優先級車道(Lanes
)和副作用標志(Flags
)。
function bubbleProperties(completedWork: Fiber) {// 如果 completedWork 存在替代節點(alternate),并且替代節點的子節點與當前節點的子節點相同,則認為可以跳過更新。const didBailout =completedWork.alternate !== null &&completedWork.alternate.child === completedWork.child;// 用于存儲合并后的子節點的渲染優先級車道,初始化為 NoLanes(表示沒有車道)。let newChildLanes: Lanes = NoLanes;// 用于存儲合并后的子節點的副作用標志,初始化為 NoFlags(表示沒有標志)。let subtreeFlags = NoFlags;// 未跳過更新的情況if (!didBailout) {let child = completedWork.child;// 使用 while 循環遍歷 completedWork 的所有子節點:while (child !== null) {// 合并子節點的 lanes 和 childLanes 到newChildLanesnewChildLanes = mergeLanes(newChildLanes,mergeLanes(child.lanes, child.childLanes),);// 將子節點的 subtreeFlags 和 flags 合并到 subtreeFlags 中subtreeFlags |= child.subtreeFlags;subtreeFlags |= child.flags;child.return = completedWork;// 繼續下一個兄弟節點child = child.sibling;}// 更新completedWork.subtreeFlags |= subtreeFlags;} else {let child = completedWork.child;while (child !== null) {// 合并子節點的 lanes 和 childLanes 到 newChildLanes 中。newChildLanes = mergeLanes(newChildLanes,mergeLanes(child.lanes, child.childLanes),);// ignore them.// 使用按位與運算符 & 過濾掉非靜態的標志(通過 StaticMask)。subtreeFlags |= child.subtreeFlags & StaticMask;subtreeFlags |= child.flags & StaticMask;child.return = completedWork;child = child.sibling;}completedWork.subtreeFlags |= subtreeFlags;}completedWork.childLanes = newChildLanes;return didBailout;
}
?<Tag>HostRoot
React 處理 HostRoot
類型節點(即應用根節點)的核心邏輯,主要負責完成根節點渲染后的環境清理、狀態同步和副作用標記。
case HostRoot: {// 1、獲取 FiberRoot 實例,React 應用的根對象,包含應用狀態、優先級隊列等核心信息。const fiberRoot = (workInProgress.stateNode: FiberRoot);//2、緩存處理if (enableCache) {let previousCache: Cache | null = null;if (current !== null) {previousCache = current.memoizedState.cache;}const cache: Cache = workInProgress.memoizedState.cache;// 比較新舊緩存對象,若不同則標記 Passive 副作用(如 useEffect)if (cache !== previousCache) {// Run passive effects to retain/release the cache.workInProgress.flags |= Passive;}// 清理緩存上下文棧popCacheProvider(workInProgress, cache);}// 3、環境狀態恢復// 彈出過渡狀態:結束當前渲染的過渡(如 startTransition)。popRootTransition(workInProgress, fiberRoot, renderLanes);// 彈出宿主容器:恢復 DOM 根節點上下文(見 popHostContainer 函數)popHostContainer(workInProgress);// 彈出遺留上下文:清理舊版 context API 的狀態。popTopLevelLegacyContextObject(workInProgress);// 4、上下文狀態同步// 在渲染過程中收集的新上下文值,在此處應用到根節點。if (fiberRoot.pendingContext) {fiberRoot.context = fiberRoot.pendingContext;fiberRoot.pendingContext = null;}//5、處理水合// 處理首次渲染或無子節點的情況if (current === null || current.child === null) {// If we hydrated, pop so that we can delete any remaining children// that weren't hydrated.// popHydrationState:清理水合過程中的臨時狀態,并返回是否成功水合。const wasHydrated = popHydrationState(workInProgress);// 水合成功if (wasHydrated) {// emitPendingHydrationWarnings();// 標記更新,可能用于處理殘留的 DOM 差異。// markUpdate(workInProgress);} else {// 未水合或水合失敗的處理if (current !== null) {const prevState: RootState = current.memoizedState;if (// Check if this is a client root// 非脫水狀態(純客戶端渲染)!prevState.isDehydrated ||// Check if we reverted to client rendering (e.g. due to an error)// 強制客戶端渲染標志(如水合錯誤)(workInProgress.flags & ForceClientRender) !== NoFlags) {// Snapshot 標志:觸發 getSnapshotBeforeUpdate 生命周期(若適用)。workInProgress.flags |= Snapshot;// upgradeHydrationErrorsToRecoverable:將水合錯誤轉換為可恢復錯誤,避免整頁崩潰。upgradeHydrationErrorsToRecoverable();}}}}// 6、更新宿主容器與冒泡屬性// 更新宿主容器:生成 DOM 更新隊列(如屬性變化、子節點增刪)。updateHostContainer(current, workInProgress);// 冒泡屬性:向上傳遞子樹的狀態(如是否有副作用、優先級信息)。bubbleProperties(workInProgress);return null;
}
updateHostContainer
updateHostContainer
是 React 中處理宿主容器(如 DOM 根節點或 Portal 容器)更新的核心函數。當啟用持久化渲染(supportsPersistence
)且需要克隆容器時,該函數會創建新的子節點集合,標記更新,并最終觸發容器的子節點替換。
function updateHostContainer(current: null | Fiber, workInProgress: Fiber) {// supportsPersistence:是否啟用持久化渲染(如 React 18+ 的并發模式)。if (supportsPersistence) {// oesRequireClone:判斷是否需要克隆容器(如子節點結構變化、屬性更新等)。if (doesRequireClone(current, workInProgress)) {// 創建新子節點集合const portalOrRoot: {containerInfo: Container,pendingChildren: ChildSet,} = workInProgress.stateNode;const container = portalOrRoot.containerInfo;// 創建用于存儲子節點的臨時集合。const newChildSet = createContainerChildSet();// If children might have changed, we have to add them all to the set.// appendAllChildrenToContainer將 workInProgress 的所有子節點添加到新集合。appendAllChildrenToContainer(newChildSet,workInProgress,/* needsVisibilityToggle */ false,/* isHidden */ false,);portalOrRoot.pendingChildren = newChildSet;// Schedule an update on the container to swap out the container.// 標記容器需要更新,觸發后續的 DOM 操作。markUpdate(workInProgress);// 準備容器的子節點更新,可能包括:// 計算新舊子節點的差異。// 準備插入 / 刪除 / 移動等 DOM 操作隊列。finalizeContainerChildren(container, newChildSet);}}
}
工具函數之 doesRequireClone
doesRequireClone
?函數用于判斷是否需要克隆宿主容器(如 DOM 節點)以應用更新。
function doesRequireClone(current: null | Fiber, completedWork: Fiber) {const didBailout = current !== null && current.child === completedWork.child;// 若組件因 bailout 機制跳過渲染(如 React.memo 優化),直接返回 false(無需克隆)。if (didBailout) {return false;}// ChildDeletion 標志:表示子樹中存在節點刪除操作,需克隆容器以安全移除節點。if ((completedWork.flags & ChildDeletion) !== NoFlags) {return true;}// TODO: If we move the `doesRequireClone` call after `bubbleProperties`// then we only have to check the `completedWork.subtreeFlags`.let child = completedWork.child;while (child !== null) {// Cloned:節點需要被克隆。// Visibility:節點可見性變化(如通過 Suspense 控制)。// Placement:節點插入或移動。// ChildDeletion:子節點刪除。// 持久化模式(enablePersistedModeClonedFlag 啟用const checkedFlags = enablePersistedModeClonedFlag? Cloned | Visibility | Placement | ChildDeletion// 傳統模式:檢查 MutationMask(包含 Placement、Update、Deletion 等基本變更)。: MutationMask;// child.flags:當前節點的變更標志。// child.subtreeFlags:子樹中所有節點的累積變更標志。if ((child.flags & checkedFlags) !== NoFlags ||(child.subtreeFlags & checkedFlags) !== NoFlags) {return true;}child = child.sibling;}return false;
}
工具函數之 createContainerChildSet
createContainerChildSet
是 React 中用于創建子節點集合的工廠函數,主要服務于宿主容器(如 DOM 根節點或 Portal)的更新過程。
createContainerChildSet(): Array<Instance | TextInstance> {return [];
},
工具函數之 appendAllChildrenToContainer
appendAllChildrenToContainer
是 React 渲染流程中的核心函數,用于將 Fiber 樹中的子節點遞歸轉換為實際渲染節點(如 DOM 元素)并添加到容器中。
function appendAllChildrenToContainer(containerChildSet: ChildSet, // 目標容器子節點集合workInProgress: Fiber, // 當前處理的 Fiber 節點needsVisibilityToggle: boolean, // 是否需要控制可見性isHidden: boolean, // 當前是否隱藏
) {if (supportsPersistence) {// We only have the top Fiber that was created but we need recurse down its// children to find all the terminal nodes.let node = workInProgress.child;while (node !== null) {// 節點類型處理// 1、HostComponent:DOM 元素(如 <div>)。if (node.tag === HostComponent) {let instance = node.stateNode;if (needsVisibilityToggle && isHidden) {// This child is inside a timed out tree. Hide it.const props = node.memoizedProps;const type = node.type;instance = cloneHiddenInstance(instance, type, props);}// 將實例添加到子節點集合中appendChildToContainerChildSet(containerChildSet, instance);// 2、處理文本} else if (node.tag === HostText) {let instance = node.stateNode;if (needsVisibilityToggle && isHidden) {// This child is inside a timed out tree. Hide it.const text = node.memoizedProps;instance = cloneHiddenTextInstance(instance, text);}appendChildToContainerChildSet(containerChildSet, instance);// 3、處理HostPortal} else if (node.tag === HostPortal) {// Portal 節點不遞歸處理子節點(由 Portal 單獨管理)// 4、處理不可見組件} else if (node.tag === OffscreenComponent &&node.memoizedState !== null) {const child = node.child;if (child !== null) {child.return = node;}// If Offscreen is not in manual mode, detached tree is hidden from user space.const _needsVisibilityToggle = !isOffscreenManual(node);appendAllChildrenToContainer(containerChildSet,node,/* needsVisibilityToggle */ _needsVisibilityToggle,/* isHidden */ true,);// 遞歸到子節點} else if (node.child !== null) {node.child.return = node;node = node.child;continue;}node = (node: Fiber);if (node === workInProgress) {return;}// 回溯到父節點while (node.sibling === null) {if (node.return === null || node.return === workInProgress) {return;}node = node.return;}node.sibling.return = node.return;node = node.sibling;}}
}
?工具函數之 appendChildToContainerChildSet
appendChildToContainerChildSet
是 React 內部用于將子節點添加到容器子節點集合的輔助函數。
appendChildToContainerChildSet(childSet: Array<Instance | TextInstance>,child: Instance | TextInstance,): void {childSet.push(child);
},
工具函數之?appendAllChildrenToContainer
?
appendAllChildrenToContainer
是 React 渲染流程中的核心函數,用于將 Fiber 樹中的子節點遞歸轉換為實際渲染節點(如 DOM 元素)并添加到容器中。該函數處理多種節點類型(組件、文本、Portal 等),支持可見性控制,并在持久化渲染模式下優化節點添加邏輯。
function appendAllChildrenToContainer(containerChildSet: ChildSet, // 目標容器子節點集合workInProgress: Fiber, // 當前處理的 Fiber 節點needsVisibilityToggle: boolean, // 是否需要控制可見性isHidden: boolean, // 當前是否隱藏
) {if (supportsPersistence) {// We only have the top Fiber that was created but we need recurse down its// children to find all the terminal nodes.let node = workInProgress.child;while (node !== null) {// 節點類型處理// 1、HostComponent:DOM 元素(如 <div>)。if (node.tag === HostComponent) {let instance = node.stateNode;if (needsVisibilityToggle && isHidden) {// This child is inside a timed out tree. Hide it.const props = node.memoizedProps;const type = node.type;instance = cloneHiddenInstance(instance, type, props);}appendChildToContainerChildSet(containerChildSet, instance);// 2、處理文本} else if (node.tag === HostText) {let instance = node.stateNode;if (needsVisibilityToggle && isHidden) {// This child is inside a timed out tree. Hide it.const text = node.memoizedProps;instance = cloneHiddenTextInstance(instance, text);}appendChildToContainerChildSet(containerChildSet, instance);// 3、處理HostPortal} else if (node.tag === HostPortal) {// Portal 節點不遞歸處理子節點(由 Portal 單獨管理)// 4、處理不可見組件} else if (node.tag === OffscreenComponent &&node.memoizedState !== null) {const child = node.child;if (child !== null) {child.return = node;}// If Offscreen is not in manual mode, detached tree is hidden from user space.const _needsVisibilityToggle = !isOffscreenManual(node);appendAllChildrenToContainer(containerChildSet,node,/* needsVisibilityToggle */ _needsVisibilityToggle,/* isHidden */ true,);// 遞歸到子節點} else if (node.child !== null) {node.child.return = node;node = node.child;continue;}node = (node: Fiber);if (node === workInProgress) {return;}// 回溯或處理兄弟節點while (node.sibling === null) {if (node.return === null || node.return === workInProgress) {return;}node = node.return;}node.sibling.return = node.return;node = node.sibling;}}
}
工具函數之?finalizeContainerChildren
?
?finalizeContainerChildren
?函數用于將新生成的子節點集合(newChildren
)關聯到容器(container
)。
finalizeContainerChildren(container: Container,newChildren: Array<Instance | TextInstance>,
): void {container.pendingChildren = newChildren;if (newChildren.length === 1 &&newChildren[0].text === 'Error when completing root') {// Trigger an error for testing purposesthrow Error('Error when completing root');}
},
<Tag>HostHoistable
處理 React 中 HostHoistable
類型的 Fiber 節點,主要用于優化和管理可提升的宿主環境組件(如 DOM 元素)。這類組件可能包含異步資源(如圖片、腳本),需要特殊的預加載和掛起邏輯。代碼根據組件是否包含資源(Resource
)以及是否處于更新階段,選擇不同的處理路徑,確保資源預加載和組件渲染的高效性與正確性。
執行過程(啟用資源管理):
一、初始化過程
- 標記更新。
- 處理資源類型。
- 處理普通實例類型。
二、更新過程
- 資源存在
- 資源存在變化。標記更新。
- 資源未發生變化。清除掛起標記。
- 普通實例
- 支持突變模式。當props發生變化化,標記更新。
- 不支持突變模式,調用updateHostComponent函數。
case HostHoistable: {if (supportsResources) {const type = workInProgress.type;const nextResource: Resource | null = workInProgress.memoizedState;// 初始化if (current === null) {// 標記更新markUpdate(workInProgress);if (nextResource !== null) {// 處理資源類型bubbleProperties(workInProgress);preloadResourceAndSuspendIfNeeded(workInProgress,nextResource,type,newProps,renderLanes,);return null;} else {// 處理普通實例bubbleProperties(workInProgress);preloadInstanceAndSuspendIfNeeded(workInProgress,type,newProps,renderLanes,);return null;}} else {// This is an update.if (nextResource) {// This is a Resourceif (nextResource !== current.memoizedState) {// 資源變化,觸發更新和預加載// we have a new Resource. we need to updatemarkUpdate(workInProgress);// This must come at the very end of the complete phase.bubbleProperties(workInProgress);preloadResourceAndSuspendIfNeeded(workInProgress,nextResource,type,newProps,renderLanes,);return null;} else {// This must come at the very end of the complete phase.bubbleProperties(workInProgress);// 資源未變化,清除掛起標志workInProgress.flags &= ~MaySuspendCommit;return null;}} else {// 更新階段且為普通實例// This is an Instance// We may have props to update on the Hoistable instance.if (supportsMutation) {const oldProps = current.memoizedProps;if (oldProps !== newProps) {markUpdate(workInProgress);}} else {// We use the updateHostComponent path becuase it produces// the update queue we need for Hoistables.updateHostComponent(current,workInProgress,type,newProps,renderLanes,);}// This must come at the very end of the complete phase.bubbleProperties(workInProgress);preloadInstanceAndSuspendIfNeeded(workInProgress,type,newProps,renderLanes,);return null;}}}// Fall through
}
?工具函數之 preloadResourceAndSuspendIfNeeded
preloadResourceAndSuspendIfNeeded
是 React 內部用于處理異步資源預加載的核心函數,主要針對 HostHoistable
類型的 Fiber 節點(如媒體元素、腳本等)。其核心邏輯是:嘗試預加載資源,如果資源未就緒則掛起當前渲染,避免阻塞主線程。該函數實現了 React 的并發渲染特性,確保在資源加載期間頁面保持響應,并根據配置決定是顯示舊內容還是立即掛起。
function preloadResourceAndSuspendIfNeeded(workInProgress: Fiber,resource: Resource,type: Type,props: Props,renderLanes: Lanes,
) {// This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.// mayResourceSuspendCommit 判斷資源是否可能導致掛起。if (!mayResourceSuspendCommit(resource)) {// 若不可能掛起(如資源已緩存),清除掛起標志并直接返回。workInProgress.flags &= ~MaySuspendCommit;return;}// 標記可能掛起狀態workInProgress.flags |= MaySuspendCommit;const isReady = preloadResource(resource);// 資源未就緒if (!isReady) {if (shouldRemainOnPreviousScreen()) {// 標記 ShouldSuspendCommit 標志,等待資源就緒后再提交更新。workInProgress.flags |= ShouldSuspendCommit;} else {// 資源已就緒// 立即調用 suspendCommit() 掛起當前渲染,觸發 Suspense 邊界的 fallback。suspendCommit();}}
}
<Tag>HostSingleton
處理 React 中 HostSingleton
類型的 Fiber 節點,用于表示必須在整個應用中唯一存在的宿主環境組件。
case HostSingleton: {// 支持單例if (supportsSingletons) {// 彈出當前 Fiber 的宿主環境上下文。popHostContext(workInProgress);// 獲取根應用節點const rootContainerInstance = getRootHostContainer();const type = workInProgress.type;// 更新if (current !== null && workInProgress.stateNode != null) {// 突變模式if (supportsMutation) {const oldProps = current.memoizedProps;if (oldProps !== newProps) {// 標記更新markUpdate(workInProgress);}} else {updateHostComponent(current,workInProgress,type,newProps,renderLanes,);}} else {// 初始化if (!newProps) {if (workInProgress.stateNode === null) {throw new Error('We must have new props for new mounts. This error is likely ' +'caused by a bug in React. Please file an issue.',);}// This can happen when we abort work.bubbleProperties(workInProgress);return null;}// 獲取上下文const currentHostContext = getHostContext();const wasHydrated = popHydrationState(workInProgress);let instance: Instance;// 服務端渲染if (wasHydrated) {// prepareToHydrateHostInstance(workInProgress, currentHostContext);// // 復用現有 DOM 節點,避免重新創建。// instance = workInProgress.stateNode;} else {// 客戶端渲染// 通過 resolveSingletonInstance 創建單例實例,并標記更新。instance = resolveSingletonInstance(type,newProps,rootContainerInstance,currentHostContext,true,);workInProgress.stateNode = instance;markUpdate(workInProgress);}}// 屬性冒泡,將子節點的狀態(如副作用標志)向上傳播到父節點。bubbleProperties(workInProgress);// 結束當前 Fiber 的處理return null;}// Fall through
}
工具函數之 resolveSingletonInstance
resolveSingletonInstance
是 React 內部用于獲取或創建單例 DOM 元素的核心函數,專門處理那些在文檔中必須唯一存在且不能被 React 直接創建的特殊元素(如 <html>
、<head>
、<body>
)。
function resolveSingletonInstance(type: string,props: Props,rootContainerInstance: Container,hostContext: HostContext,validateDOMNestingDev: boolean,
): Instance {// 獲取document對象const ownerDocument = getOwnerDocumentFromRootContainer(rootContainerInstance,);switch (type) {case 'html': {const documentElement = ownerDocument.documentElement;if (!documentElement) {throw new Error('React expected an <html> element (document.documentElement) to exist in the Document but one was' +' not found. React never removes the documentElement for any Document it renders into so' +' the cause is likely in some other script running on this page.',);}return documentElement;}case 'head': {const head = ownerDocument.head;if (!head) {throw new Error('React expected a <head> element (document.head) to exist in the Document but one was' +' not found. React never removes the head for any Document it renders into so' +' the cause is likely in some other script running on this page.',);}return head;}case 'body': {const body = ownerDocument.body;if (!body) {throw new Error('React expected a <body> element (document.body) to exist in the Document but one was' +' not found. React never removes the body for any Document it renders into so' +' the cause is likely in some other script running on this page.',);}return body;}default: {throw new Error('resolveSingletonInstance was called with an element type that is not supported. This is a bug in React.',);}}
}
<Tag>HostComponent
React 處理 HostComponent
(如 DOM 元素)的核心邏輯,主要負責創建或更新實際的 DOM 節點。它根據節點是否已存在、是否來自服務端渲染水合等情況,選擇不同的處理路徑,并處理特殊屬性(如自動聚焦)和預加載邏輯。
case HostComponent: {// 退出當前宿主環境上下文棧popHostContext(workInProgress);// 獲取組件類型,類如div,spanconst type = workInProgress.type;// 更新已存在的 DOM 節點if (current !== null && workInProgress.stateNode != null) {// 調用 updateHostComponent 更新 DOM 屬性(如 className、style)。處理事件監聽器的添加 / 移除。updateHostComponent(current,workInProgress,type,newProps,renderLanes,);// 首次渲染或新增} else {if (!newProps) {// 錯誤處理:新節點必須有 propsif (workInProgress.stateNode === null) {throw new Error('We must have new props for new mounts. This error is likely ' +'caused by a bug in React. Please file an issue.',);}// This can happen when we abort work.bubbleProperties(workInProgress);return null;}const currentHostContext = getHostContext();const wasHydrated = popHydrationState(workInProgress);if (wasHydrated) {// prepareToHydrateHostInstance(workInProgress, currentHostContext);} else {// 純客戶端渲染邏輯const rootContainerInstance = getRootHostContainer();// 調用 createInstance 創建新 DOM 元素。const instance = createInstance(type,newProps,rootContainerInstance,currentHostContext,workInProgress,);markCloned(workInProgress);// 通過 appendAllChildren 添加子元素。appendAllChildren(instance, workInProgress, false, false);workInProgress.stateNode = instance;if (finalizeInitialChildren(instance,type,newProps,currentHostContext,)) {markUpdate(workInProgress);}}}// 屬性冒泡bubbleProperties(workInProgress);// 預加載preloadInstanceAndSuspendIfNeeded(workInProgress,workInProgress.type,workInProgress.pendingProps,renderLanes,);return null;
}
updateHostComponent
updateHostComponent
是 React 處理宿主組件(如 DOM 元素)更新的核心函數,根據渲染模式(突變模式或持久化模式)選擇不同的更新策略。在突變模式下,直接標記節點更新;在持久化模式下,通過克隆實例實現無突變更新,并支持狀態保留。
function updateHostComponent(current: Fiber,workInProgress: Fiber,type: Type,newProps: Props,renderLanes: Lanes,
) {// 突變模式下的處理(supportsMutation)if (supportsMutation) {const oldProps = current.memoizedProps;// 比較新舊屬性引用是否相同(淺比較)。if (oldProps === newProps) {return;}// 標記節點為更新狀態markUpdate(workInProgress);// 持久化模式下的處理(supportsPersistence)} else if (supportsPersistence) {const currentInstance = current.stateNode;const oldProps = current.memoizedProps;// 通過 doesRequireClone 檢查節點是否需要克隆。const requiresClone = doesRequireClone(current, workInProgress);if (!requiresClone && oldProps === newProps) {workInProgress.stateNode = currentInstance;return;}const currentHostContext = getHostContext();let newChildSet = null;if (requiresClone && passChildrenWhenCloningPersistedNodes) {markCloned(workInProgress);newChildSet = createContainerChildSet();// If children might have changed, we have to add them all to the set.appendAllChildrenToContainer(newChildSet,workInProgress,/* needsVisibilityToggle */ false,/* isHidden */ false,);}// 調用 cloneInstance 創建新實例,保留舊狀態。const newInstance = cloneInstance(currentInstance,type,oldProps,newProps,!requiresClone,newChildSet,);if (newInstance === currentInstance) {// 無需變更,復用workInProgress.stateNode = currentInstance;return;} else {// 標記為克隆節點markCloned(workInProgress);}// 處理特殊屬性(如自動聚焦)if (finalizeInitialChildren(newInstance, type, newProps, currentHostContext)) {markUpdate(workInProgress);}// 更新子節點workInProgress.stateNode = newInstance;if (!requiresClone) {if (!enablePersistedModeClonedFlag) {markUpdate(workInProgress);}} else if (!passChildrenWhenCloningPersistedNodes) {// If children have changed, we have to add them all to the set.appendAllChildren(newInstance,workInProgress,/* needsVisibilityToggle */ false,/* isHidden */ false,);}}
}
createInstance
createInstance
方法,用于創建虛擬 DOM 元素的實例。該方法生成一個包含元素類型、屬性和子節點信息的對象,并處理特殊屬性(如 hidden
、src
)和文本內容。通過不可枚舉屬性存儲內部狀態,確保這些信息不會暴露給外部代碼。
const sharedHostConfig = {createInstance(type: string,//元素的類型props: Props,// 傳遞給元素的屬性對象。rootContainerInstance: Container,// 根容器實例,即渲染的目標容器。hostContext: HostContext,// 當前渲染的宿主環境上下文。internalInstanceHandle: Object,// 指向 React 內部 Fiber 節點的引用。): Instance {if (type === 'errorInCompletePhase') {throw new Error('Error in host config.');}// 實例結構const inst = {id: instanceCounter++,type: type,children: [],parent: -1,// shouldSetTextContent:判斷是否應將子節點作為文本內容處理。text: shouldSetTextContent(type, props)? // eslint-disable-next-line react-internal/safe-string-coercion// computeText:處理文本內容(可能包括轉義、格式化等)。computeText((props.children: any) + '', hostContext): null,prop: props.prop,hidden: !!props.hidden,context: hostContext,};// 為 suspensey-thing 類型的元素添加 src 屬性。if (type === 'suspensey-thing' && typeof props.src === 'string') {inst.src = props.src;}// Hide from unit tests// 將 id、parent、text 等屬性設為不可枚舉。// 防止這些內部狀態在 for...in 循環或 JSON.stringify 中暴露。Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});Object.defineProperty(inst, 'parent', {value: inst.parent,enumerable: false,});Object.defineProperty(inst, 'text', {value: inst.text,enumerable: false,});Object.defineProperty(inst, 'context', {value: inst.context,enumerable: false,});Object.defineProperty(inst, 'fiber', {value: internalInstanceHandle,enumerable: false,});return inst;},
}
關鍵屬性:
id
:唯一標識,用于內部跟蹤。type
:元素類型(如div
、span
)。children
:子節點數組。text
:文本內容(如果適用)。hidden
:是否隱藏(基于props.hidden
)。
const inst = {id: instanceCounter++,type: type,children: [],parent: -1,// shouldSetTextContent:判斷是否應將子節點作為文本內容處理。text: shouldSetTextContent(type, props)? // eslint-disable-next-line react-internal/safe-string-coercion// computeText:處理文本內容(可能包括轉義、格式化等)。computeText((props.children: any) + '', hostContext): null,prop: props.prop,hidden: !!props.hidden,context: hostContext,
};
工具函數之?shouldSetTextContent?
function shouldSetTextContent(type: string, props: Props): boolean {if (type === 'errorInBeginPhase') {throw new Error('Error in host config.');}return (typeof props.children === 'string' ||typeof props.children === 'number' ||typeof props.children === 'bigint');
}
工具函數之?computeText
?
computeText
是一個文本處理函數,用于根據宿主環境上下文決定是否轉換文本大小寫。當上下文為 UPPERCASE_CONTEXT
時,將文本轉換為大寫;否則保持原文。?
function computeText(rawText, hostContext) {return hostContext === UPPERCASE_CONTEXT ? rawText.toUpperCase() : rawText;
}const UPPERCASE_CONTEXT = {};
工具函數之 finalizeInitialChildren
finalizeInitialChildren
?函數主要用于完成元素初始化后的最終處理。
它執行以下核心操作:
- 通過
setInitialProperties
設置元素的初始屬性(如className
、style
)。 - 根據元素類型判斷是否需要在渲染后執行副作用操作(如自動聚焦、圖片預加載)。
- 返回布爾值,指示該元素是否需要在提交階段(commit phase)執行額外的副作用。
function finalizeInitialChildren(domElement: Instance,type: string,props: Props,hostContext: HostContext,
): boolean {setInitialProperties(domElement, type, props);switch (type) {case 'button':case 'input':case 'select':case 'textarea':return !!props.autoFocus;case 'img':return true;default:return false;}
}
工具函數之 appendAllChildren
appendAllChildren
函數用于將 Fiber 樹中的所有子節點掛載到父 DOM 節點上。它通過深度優先遍歷 Fiber 樹,將真實 DOM 節點(如 HostComponent
和 HostText
)按正確順序添加到父容器中。該函數針對不同渲染模式(突變模式和持久化模式)進行了優化。
處理過程:
一、突變模式
- 處理當前子節點
- 如果節點tag是 HostComponent 或者 HostText,直接將 HostComponent(DOM 元素)或 HostText(文本節點)直接添加到父容器。
- 如果節點tag是?HostPortal,不做處理。(Portal 節點不直接添加到父容器,而是通過 Portal 機制掛載到其他容器)
- 如果當前子節點有子節點,則進入遞歸處理(則為下面的第2點)
- 遞歸處理子節點
- 如果當前子節點正是正在處理的fiber(workInProgress),則退出當前循環。
- 回溯或處理兄弟節點
二、持久化模式
- 處理當前子節點
- 如果節點tag是 HostComponent ,當需要隱藏時克隆節點并應用隱藏樣式直接,然后將克隆實例 instance 直接添加到父容器。
- 如果節點tag是 HostText ,當需要隱藏時克隆節點并應用隱藏樣式直接,然后將克隆實例 instance 直接添加到父容器。
- 如果節點tag是?HostPortal,不做處理。(Portal 節點不直接添加到父容器,而是通過 Portal 機制掛載到其他容器)
- 如果節點tag是?OffscreenComponent,處理其子節點與子節點的引用關系,調用appendAllChildren遞歸處理子節點。
- 如果當前子節點有子節點,則進入遞歸處理(則為下面的第2點)
- 遞歸處理子節點
- 如果當前子節點正是正在處理的fiber(workInProgress),則退出當前循環。
- 回溯或處理兄弟節點
function appendAllChildren(parent: Instance,// 指定子節點要掛載到的父容器。workInProgress: Fiber,// 當前正在處理的 Fiber 節點,作為遍歷起點。needsVisibilityToggle: boolean, //指示是否需要根據 isHidden 參數調整子節點的可見性。isHidden: boolean, // 當 needsVisibilityToggle 為 true 時,決定子節點是否應被隱藏。
) {// 突變模式處理(supportsMutation)if (supportsMutation) {let node = workInProgress.child;while (node !== null) {// 處理當前節點if (node.tag === HostComponent || node.tag === HostText) {// 將 HostComponent(DOM 元素)或 HostText(文本節點)直接添加到父容器。appendInitialChild(parent, node.stateNode);} else if (node.tag === HostPortal ||(supportsSingletons ? node.tag === HostSingleton : false)) {// Portal 節點不直接添加到父容器,而是通過 Portal 機制掛載到其他容器// 遞歸處理子節點} else if (node.child !== null) {node.child.return = node;node = node.child;continue;}if (node === workInProgress) {return;}// 回溯或處理兄弟節點while (node.sibling === null) {if (node.return === null || node.return === workInProgress) {return;}node = node.return;}node.sibling.return = node.return;node = node.sibling;}// 持久化模式處理(supportsPersistence)} else if (supportsPersistence) {// We only have the top Fiber that was created but we need recurse down its// children to find all the terminal nodes.let node = workInProgress.child;while (node !== null) {if (node.tag === HostComponent) {let instance = node.stateNode;if (needsVisibilityToggle && isHidden) {// 當節點位于超時的 Suspense 邊界內時,克隆節點并應用隱藏樣式。const props = node.memoizedProps;const type = node.type;instance = cloneHiddenInstance(instance, type, props);}//將 instance 直接添加到父容器。appendInitialChild(parent, instance);} else if (node.tag === HostText) {let instance = node.stateNode;if (needsVisibilityToggle && isHidden) {const text = node.memoizedProps;instance = cloneHiddenTextInstance(instance, text);}appendInitialChild(parent, instance);} else if (node.tag === HostPortal) {// Portal 節點不直接添加到父容器,而是通過 Portal 機制掛載到其他容器} else if (node.tag === OffscreenComponent &&node.memoizedState !== null) {const child = node.child;if (child !== null) {child.return = node;}appendAllChildren(parent,node,/* needsVisibilityToggle */ true,/* isHidden */ true,);// 遞歸處理子節點} else if (node.child !== null) {node.child.return = node;node = node.child;continue;}if (node === workInProgress) {return;}// 回溯或處理兄弟節點while (node.sibling === null) {if (node.return === null || node.return === workInProgress) {return;}node = node.return;}node.sibling.return = node.return;node = node.sibling;}}
}
?工具函數之?appendInitialChild
function appendInitialChild(parentInstance: Instance,child: Instance | TextInstance,
): void {parentInstance.appendChild(child);
}
?工具函數之?cloneHiddenInstance
cloneHiddenInstance 方法,用于創建一個隱藏版本的 DOM 元素實例。該方法在需要臨時隱藏元素但保留其結構和狀態時使用。
const hostConfig = useMutation ? { cloneHiddenInstance(instance: Instance,type: string,props: Props,): Instance {const clone = cloneInstance(instance, type, props, props, true, null);clone.hidden = true;return clone;},
}
工具函數之 cloneInstance
cloneInstance
是 React 內部用于克隆 DOM 元素實例的函數,主要用于在不改變原始實例的情況下創建一個具有新屬性的副本
function cloneInstance(instance: Instance,type: string,oldProps: Props,newProps: Props,keepChildren: boolean,children: ?$ReadOnlyArray<Instance>,
): Instance {// 實例克隆核心邏輯const clone = {id: instance.id,type: type,parent: instance.parent,children: keepChildren ? instance.children : children ?? [],text: shouldSetTextContent(type, newProps)? computeText((newProps.children: any) + '', instance.context): null,prop: newProps.prop,hidden: !!newProps.hidden,context: instance.context,};// 當元素類型為 suspensey-thing 且新屬性包含 src 時,添加 src 屬性到克隆實例。if (type === 'suspensey-thing' && typeof newProps.src === 'string') {clone.src = newProps.src;}// 不可枚舉屬性設置// 將關鍵內部屬性設為不可枚舉,避免在遍歷或序列化時暴露。
// 保持與原始實例的行為一致性。Object.defineProperty(clone, 'id', {value: clone.id,enumerable: false,});Object.defineProperty(clone, 'parent', {value: clone.parent,enumerable: false,});Object.defineProperty(clone, 'text', {value: clone.text,enumerable: false,});Object.defineProperty(clone, 'context', {value: clone.context,enumerable: false,});hostCloneCounter++;return clone;
}
工具函數之 cloneHiddenTextInstance
cloneHiddenTextInstance
方法,專門用于克隆文本節點并將其標記為隱藏狀態。用在處理需要臨時隱藏文本內容但保留其結構和上下文的場景時。
const hostConfig = useMutation ? {cloneHiddenTextInstance(instance: TextInstance,text: string,): TextInstance {const clone = {text: instance.text,id: instance.id,parent: instance.parent,hidden: true,context: instance.context,};// Hide from unit testsObject.defineProperty(clone, 'id', {value: clone.id,enumerable: false,});Object.defineProperty(clone, 'parent', {value: clone.parent,enumerable: false,});Object.defineProperty(clone, 'context', {value: clone.context,enumerable: false,});return clone;},
}
preloadInstanceAndSuspendIfNeeded
preloadInstanceAndSuspendIfNeeded
?函數負責在元素實例化階段預加載資源并決定是否需要掛起渲染。
它通過以下步驟實現精細控制:
- 判斷元素是否可能觸發提交階段的掛起(如包含異步資源)。
- 嘗試預加載資源并檢查就緒狀態。
- 根據資源狀態和屏幕過渡策略,決定是繼續渲染、掛起并保持當前屏幕,還是立即掛起并顯示 fallback。
function preloadInstanceAndSuspendIfNeeded(workInProgress: Fiber,type: Type,props: Props,renderLanes: Lanes,
) {if (!maySuspendCommit(type, props)) {// MaySuspendCommit:表示該 Fiber 節點可能會暫停提交操作。在 React 的并發模式下,渲染過程可能會被暫停和恢復workInProgress.flags &= ~MaySuspendCommit;return;}workInProgress.flags |= MaySuspendCommit;const isReady = preloadInstance(type, props);if (!isReady) {if (shouldRemainOnPreviousScreen()) {workInProgress.flags |= ShouldSuspendCommit;} else {suspendCommit();}}
}
工具函數之 preloadResource
preloadResource
是一個用于資源預加載控制的函數,主要決定特定資源是否需要暫停渲染(suspend)。核心邏輯是:當資源類型為樣式表(stylesheet)且處于未加載狀態時,函數返回 false
觸發渲染暫停;否則返回 true
繼續渲染。
function preloadResource(resource: Resource): boolean {if (resource.type === 'stylesheet' &&(resource.state.loading & Settled) === NotLoaded) {// Return false to indicate this resource should suspendreturn false;}// Return true to indicate this resource should not suspendreturn true;
}
<Tag>HostText
React 處理 HostText
類型節點(即文本節點)的核心邏輯,主要負責更新或創建文本節點實例。它會根據節點是否已存在、是否來自服務端渲染水合等情況,選擇不同的處理路徑,確保文本內容正確地反映到 DOM 中。
case HostText: {// 獲取新舊文本內容const newText = newProps;// 更新if (current && workInProgress.stateNode != null) {const oldText = current.memoizedProps;// 調用 updateHostText 更新現有文本節點內容(如 setTextContent)。updateHostText(current, workInProgress, oldText, newText);// 首次渲染} else {// 處理新創建的文本節點if (typeof newText !== 'string') {if (workInProgress.stateNode === null) {throw new Error('We must have new props for new mounts. This error is likely ' +'caused by a bug in React. Please file an issue.',);}// This can happen when we abort work.}// 獲取當前渲染的根容器(如 DOM 中的 root 節點)。const rootContainerInstance = getRootHostContainer();// 獲取宿主環境上下文const currentHostContext = getHostContext();// 處理服務端渲染水合狀態const wasHydrated = popHydrationState(workInProgress);// 檢查當前節點是否來自服務端渲染的 HTML。if (wasHydrated) {// prepareToHydrateHostTextInstance(workInProgress);} else {// 添加Cloned標記markCloned(workInProgress);// 創建文本實例workInProgress.stateNode = createTextInstance(newText,rootContainerInstance,currentHostContext,workInProgress,);}}// 冒泡屬性與完成處理bubbleProperties(workInProgress);return null;
}
updateHostText
updateHostText
是 React 處理文本節點更新的核心函數,根據渲染模式(突變模式或持久化模式)選擇不同的更新策略。當文本內容發生變化時,突變模式直接標記更新,而持久化模式則創建新的文本實例并標記克隆。這一機制確保了在不同渲染模式下文本更新的高效性和正確性。
function updateHostText(current: Fiber,workInProgress: Fiber,oldText: string,newText: string,
) {// 突變模式下的處理(supportsMutation)if (supportsMutation) {// If the text differs, mark it as an update. All the work in done in commitWork.// 當新舊文本內容不一致時,標記節點為 Update。if (oldText !== newText) {markUpdate(workInProgress);}// 持久化模式下的處理(supportsPersistence)} else if (supportsPersistence) {if (oldText !== newText) {// If the text content differs, we'll create a new text instance for it.const rootContainerInstance = getRootHostContainer();const currentHostContext = getHostContext();markCloned(workInProgress);// 創建新的文本實例workInProgress.stateNode = createTextInstance(newText,rootContainerInstance,currentHostContext,workInProgress,);// 確保父節點知道子節點已變更if (!enablePersistedModeClonedFlag) {// We'll have to mark it as having an effect, even though we won't use the effect for anything.// This lets the parents know that at least one of their children has changed.markUpdate(workInProgress);}} else {// 文本未變化,復用現有實例workInProgress.stateNode = current.stateNode;}}
}
工具函數之?markUpdate
function markUpdate(workInProgress: Fiber) {workInProgress.flags |= Update;
}
工具函數之?markCloned
function markCloned(workInProgress: Fiber) {if (supportsPersistence && enablePersistedModeClonedFlag) {// 添加 Cloned 標志(flags |= Cloned)。workInProgress.flags |= Cloned;}
}
工具函數之?getRootHostContainer
getRootHostContainer
?是 React 渲染系統中的基礎函數,用于獲取當前渲染上下文的根宿主容器。在瀏覽器環境中,這通常對應于?ReactDOM.render
?或?ReactDOM.createRoot
?指定的 DOM 節點(如?<div id="root"></div>
)。
function getRootHostContainer(): Container {const rootInstance = requiredContext(rootInstanceStackCursor.current);return rootInstance;
}
const rootInstanceStackCursor: StackCursor<Container | null> = createCursor(null);
function createCursor<T>(defaultValue: T): StackCursor<T> {return {current: defaultValue,};
}
function requiredContext<Value>(c: Value | null): Value {return (c: any);
}
?工具函數之?getHostContext
getHostContext()
是 React 渲染系統中的核心函數,用于獲取當前宿主環境的上下文信息。這些信息包括命名空間(如 HTML/SVG)、樣式作用域、容器配置等,是正確創建和渲染 DOM 元素的關鍵依據。通過維護一個上下文棧,React 能夠在嵌套渲染(如 SVG 內部元素)時動態切換環境配置,確保元素正確渲染。
function getHostContext(): HostContext {const context = requiredContext(contextStackCursor.current);return context;
}
const contextStackCursor: StackCursor<Object> =createCursor(emptyContextObject);const emptyContextObject: {} = {};
createTextInstance
?createTextInstance
?是 React 渲染系統中用于創建文本節點的核心函數。它通過宿主環境(如瀏覽器 DOM)提供的 API 創建實際的文本節點,并建立該節點與 React 內部 Fiber 節點的映射關系。這一過程是將虛擬 DOM 轉換為真實 DOM 的關鍵步驟
function createTextInstance(text: string,rootContainerInstance: Container,hostContext: HostContext,internalInstanceHandle: Object,
): TextInstance {// 創建新文本節點,調用 document.createTextNode(text) 創建實際的文本節點。const textNode: TextInstance = getOwnerDocumentFromRootContainer(rootContainerInstance,).createTextNode(text);// 建立 Fiber 節點與 DOM 節點的映射precacheFiberNode(internalInstanceHandle, textNode);return textNode;
}
?工具函數之?getOwnerDocumentFromRootContainer
獲取根容器所屬的 document
對象。
function getOwnerDocumentFromRootContainer(rootContainerElement: Element | Document | DocumentFragment,
): Document {return rootContainerElement.nodeType === DOCUMENT_NODE? (rootContainerElement: any): rootContainerElement.ownerDocument;
}
工具函數之?precacheFiberNode
建立 Fiber 節點與 DOM 節點的映射?
function precacheFiberNode(hostInst: Fiber,node: Instance | TextInstance | SuspenseInstance | ReactScopeInstance,
): void {(node: any)[internalInstanceKey] = hostInst;
}
const randomKey = Math.random().toString(36).slice(2);
const internalInstanceKey = '__reactFiber$' + randomKey;
<Tag>HostPortal
處理 React 中 HostPortal
類型的 Fiber 節點,用于管理通過 ReactDOM.createPortal
創建的 Portal 組件。Portal 允許將子組件渲染到 DOM 樹的不同位置(如獨立的模態框、懸浮層),但保持與父組件的上下文關系。
執行過程:
- 彈出 Portal 容器的上下文
- 更新 Portal 容器的狀態
- 準備 Portal 掛載(首次渲染時)
- 冒泡屬性以傳播副作用和狀態
case HostPortal:
// 從上下文棧中彈出當前 Portal 的容器信息。popHostContainer(workInProgress);// 比較新舊 Portal 容器的屬性差異。標記需要執行的副作用(如屬性更新、事件綁定)。updateHostContainer(current, workInProgress);// 首次掛載處理if (current === null) {//確保目標容器存在且可訪問。注冊 Portal 相關的事件處理(如點擊穿透)。應用初始樣式或動畫。preparePortalMount(workInProgress.stateNode.containerInfo);}// 屬性冒泡,將 Portal 子樹的狀態(如副作用標志、優先級)傳播到父節點。bubbleProperties(workInProgress);return null;
工具函數之?preparePortalMount?
function preparePortalMount(portalInstance: Instance): void {listenToAllSupportedEvents(portalInstance);
}
工具函數之?listenToAllSupportedEvents?
主要功能是為 React 應用的指定容器添加對所有支持的原生事件的監聽。它會對事件監聽進行去重處理,避免重復添加相同的事件監聽器。同時,對于 selectionchange
事件會進行特殊處理,因為該事件不會冒泡,需要綁定到 document
對象上。?(詳細的后續寫一篇記錄react合成事件)
function listenToAllSupportedEvents(rootContainerElement: EventTarget) {// 檢查 rootContainerElement 是否已經添加了事件監聽器。如果沒有,則將 listeningMarker 作為屬性添加到 rootContainerElement 上,并將其值設為 true,表示已經添加了事件監聽器。if (!(rootContainerElement: any)[listeningMarker]) {(rootContainerElement: any)[listeningMarker] = true;// allNativeEvents 是一個集合,包含了 React 支持的所有原生事件。// 遍歷所有支持的原生事件allNativeEvents.forEach(domEventName => {if (domEventName !== 'selectionchange') {if (!nonDelegatedEvents.has(domEventName)) {listenToNativeEvent(domEventName, false, rootContainerElement);}listenToNativeEvent(domEventName, true, rootContainerElement);}});// 獲取頂層document對象const ownerDocument =(rootContainerElement: any).nodeType === DOCUMENT_NODE? rootContainerElement: (rootContainerElement: any).ownerDocument;if (ownerDocument !== null) {// The selectionchange event also needs deduplication// but it is attached to the document.if (!(ownerDocument: any)[listeningMarker]) {(ownerDocument: any)[listeningMarker] = true;listenToNativeEvent('selectionchange', false, ownerDocument);}}}
}
全局常量(根節點狀態)
?React 渲染根節點(Root)的狀態機,用于表示整個渲染過程中可能處于的不同階段和結果。
type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
const RootInProgress = 0; // 根節點正在進行渲染
const RootFatalErrored = 1; // 渲染過程中發生致命錯誤,無法恢復。
const RootErrored = 2; //渲染過程中發生可恢復的錯誤。
const RootSuspended = 3; // 渲染被掛起,等待異步資源(如數據或代碼)。
const RootSuspendedWithDelay = 4; // 渲染被掛起,但設置了延遲顯示加載狀態。
const RootCompleted = 5; // 渲染成功完成,所有工作已提交到 DOM。
const RootDidNotComplete = 6; // 渲染未完成,可能被更高優先級的更新中斷。
?全局常量(組件類型)
?React 內部用于標識不同類型組件和節點的常量(稱為?FiberTag
)。每個常量對應一種特定的組件或節點類型。
export const FunctionComponent = 0;
export const ClassComponent = 1;export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;export const SuspenseComponent = 13;export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;
export const TracingMarkerComponent = 25;export const HostHoistable = 26;
export const HostSingleton = 27;export const IncompleteFunctionComponent = 28;
export const Throw = 29;
?全局常量(節點的狀態和操作位掩碼)
標記節點狀態和操作的位掩碼常量(Flags)。每個標志都是一個二進制值,通過位運算可以高效地組合、檢查和修改這些標志。
// 無操作(初始狀態)。
const NoFlags = /* */ 0b0000000000000000000000000000;
// 組件已執行工作(用于優化)。
const PerformedWork = /* */ 0b0000000000000000000000000001;
// 需要插入或移動 DOM 節點。
const Placement = /* */ 0b0000000000000000000000000010;
// 組件捕獲到錯誤(用于錯誤邊界)。
const DidCapture = /* */ 0b0000000000000000000010000000;
// 需要重置節點內容(如文本節點內容完全改變)。
const Hydrating = /* */ 0b0000000000000001000000000000;// You can change the rest (and add more).
// 需要更新 DOM 節點屬性(如 props 變化)。
const Update = /* */ 0b0000000000000000000000000100;
// Fiber 被克隆(如列表重排序時復用節點)。
const Cloned = /* */ 0b0000000000000000000000001000;// 需要刪除子節點。
const ChildDeletion = /* */ 0b0000000000000000000000010000;
// 需要重置節點內容(如文本節點內容完全改變)。
const ContentReset = /* */ 0b0000000000000000000000100000;
// 需要執行回調(如 useEffect、useLayoutEffect)。
const Callback = /* */ 0b0000000000000000000001000000;
/* Used by DidCapture: 0b0000000000000000000010000000; */// 強制客戶端渲染(如 SSR 水合失敗)。const ForceClientRender = /* */ 0b0000000000000000000100000000;
// 需要處理 ref 關聯或解關聯。const Ref = /* */ 0b0000000000000000001000000000;
// 需要捕獲 DOM 快照(如 getSnapshotBeforeUpdate)。const Snapshot = /* */ 0b0000000000000000010000000000;
// 表示存在被動副作用(如 useEffect),需要異步執行。const Passive = /* */ 0b0000000000000000100000000000;
/* Used by Hydrating: 0b0000000000000001000000000000; */// 組件可見性變化(如 Suspense 顯示 / 隱藏)。const Visibility = /* */ 0b0000000000000010000000000000;
// 狀態一致性標記(用于并發模式)。const StoreConsistency = /* */ 0b0000000000000100000000000000;
全局常量
RefStatic
:用于標記與ref
相關的靜態狀態。在 React 中,ref
用于訪問 DOM 節點或組件實例,這個標志可能表示ref
相關的操作或狀態是靜態的,不需要在每次渲染時重新處理。LayoutStatic
:表示與布局相關的靜態狀態。布局操作可能涉及到元素的位置、大小等信息,這個標志可能用于指示某些布局信息在渲染過程中是靜態的,不需要頻繁更新。PassiveStatic
:與被動副作用相關的靜態狀態。被動副作用通常是指那些在渲染完成后異步執行的副作用,如useEffect
鉤子中的某些操作,這個標志可能表示這些副作用的狀態是靜態的。MaySuspendCommit
:表示該Fiber
節點可能會暫停提交操作。在 React 的并發模式下,渲染過程可能會被暫停和恢復,這個標志用于標記那些可能會導致提交操作暫停的節點。
const StaticMask =LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;const RefStatic = /* */ 0b0000001000000000000000000000;
const LayoutStatic = /* */ 0b0000010000000000000000000000;
const PassiveStatic = /* */ 0b0000100000000000000000000000;
const MaySuspendCommit = /* */ 0b0001000000000000000000000000;