React19源码系列之渲染阶段performUnitOfWork
在 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;