React19源码系列之 Diff算法
在之前文章中root.render执行的过程,beginWork函数是渲染过程的核心,其针对不同类型的fiber进行不同的更新处理,在FunctionComponent(函数组件)中,会针对新旧fiber进行对比处理生成新fiber。因此此次就详细记录一下新旧节点对比生成新fiber的过程,即diff 的过程。
其实react的 diff 方法无法是首次渲染还是更新的情况下都是调用 reconcileChildFibersImpl函数。
reconcileChildren
reconcileChildren 函数是 React 协调过程中的核心函数之一,主要用于协调新旧 Fiber 节点的子节点。在 React 的渲染流程中,当一个 Fiber 节点需要更新其子节点时,该函数会根据是否存在 旧的 Fiber 节点(current),选择不同的方式来处理子节点的更新,最终将处理后的子 Fiber 节点挂载到 workInProgress 节点上。
function reconcileChildren(current: Fiber | null,// 表示旧的 Fiber 节点。workInProgress: Fiber,// 表示当前正在处理的新 Fiber 节点nextChildren: any,// 表示新的子元素集合,通常是组件返回的 JSX 元素或子组件。renderLanes: Lanes,// 渲染的优先级车道。
) {if (current === null) {workInProgress.child = mountChildFibers(workInProgress,null,nextChildren,renderLanes,);} else {workInProgress.child = reconcileChildFibers(workInProgress,current.child,nextChildren,renderLanes,);}
}
mountChildFibers函数和 reconcileChildFibers 函数都有调用高阶函数 createChildReconciler 返回的一个函数。
const mountChildFibers: ChildReconciler = createChildReconciler(false);
const reconcileChildFibers: ChildReconciler = createChildReconciler(true);
createChildReconciler
createChildReconciler 函数是一个高阶函数,其主要作用是创建并返回一个用于协调子 Fiber 节点的函数 reconcileChildFibers。
function createChildReconciler(// shouldTrackSideEffects首次渲染为false,更新为trueshouldTrackSideEffects: boolean,
): ChildReconciler {//。。。// function reconcileChildFibers(){}return reconcileChildFibers;
}
reconcileChildFibers
reconcileChildFibers 函数主要用于对比新旧子节点,生成新的 Fiber 节点树,以此实现 React 组件树的更新。主要实现是调用内部的 reconcileChildFibersImpl 函数完成调和操作,最终返回 新的第一个子 Fiber 节点。
函数参数含义:
- returnFiber,父 Fiber 节点。
- currentFirstChild,当前的子 Fiber 节点。
- newChild,新的子节点。
- lanes,渲染优先级。
function reconcileChildFibers(returnFiber: Fiber,// 父节点fibercurrentFirstChild: Fiber | null,//当前父 Fiber 节点的第一个子 Fiber 节点,如果没有子节点则为 null。newChild: any,// 新的节点lanes: Lanes,// 渲染优先级
): Fiber | null {try {// 根据新旧子节点的差异生成新的 Fiber 节点树,并返回第一个子 Fiber 节点。const firstChildFiber = reconcileChildFibersImpl(returnFiber,currentFirstChild,newChild,lanes,);return firstChildFiber;}catch{}
}
reconcileChildFibersImpl
根据新子节点的类型(如 React 元素、数组、可迭代对象、Promise 等),调用相应的调和函数来生成新的子 Fiber 节点,并返回第一个子 Fiber 节点或null。
function reconcileChildFibersImpl(returnFiber: Fiber,// 当前正在处理的父 Fiber 节点。currentFirstChild: Fiber | null,// 当前父 Fiber 节点的第一个子 Fiber 节点,如果没有子节点则为 null。newChild: any,//新的子节点信息,可以是不同类型的数据,如 React 元素、数组、可迭代对象等。lanes: Lanes,// 渲染优先级车道
): Fiber | null {// 判断新子节点是否为无 key 的顶级 Fragment 组件类型,如果是,则将新子节点更新为其 props.children,因为 Fragment 本身只是一个占位符,实际的子节点内容在其 props.children 中。const isUnkeyedTopLevelFragment =typeof newChild === 'object' &&newChild !== null &&newChild.type === REACT_FRAGMENT_TYPE &&newChild.key === null;// fragment 占位组件,将其child设置为newChildif (isUnkeyedTopLevelFragment) {// validateFragmentProps(newChild, null, returnFiber);newChild = newChild.props.children;}// Handle object types// 处理对象类型的子节点if (typeof newChild === 'object' && newChild !== null) {switch (newChild.$$typeof) {// 根据 $$typeof 判断 React 元素类型case REACT_ELEMENT_TYPE: {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const firstChild = placeSingleChild(reconcileSingleElement(returnFiber,currentFirstChild,newChild,lanes,),);// currentDebugInfo = prevDebugInfo;return firstChild;}// 处理 React Portal 类型case REACT_PORTAL_TYPE:return placeSingleChild(reconcileSinglePortal(returnFiber,currentFirstChild,newChild,lanes,),);// 处理 React Lazy 类型case REACT_LAZY_TYPE: {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);let result;// newChild._payload 是传递给初始化函数的数据const payload = newChild._payload;// newChild._init 是懒加载组件的初始化函数。const init = newChild._init;// 调用初始化函数 init 并传入 payload,以获取实际的组件。通常,init 函数会动态导入组件并返回。result = init(payload);// reconcileChildFibersImpl:这是一个内部函数,用于调和子 Fiber 节点。它会比较新旧 Fiber 树,找出差异并更新 Fiber 树。const firstChild = reconcileChildFibersImpl(returnFiber,currentFirstChild,result,lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}}// 处理数组类型的子节点if (isArray(newChild)) {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const firstChild = reconcileChildrenArray(returnFiber,currentFirstChild,newChild,lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}// 处理可迭代类型的子节点if (getIteratorFn(newChild)) {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const firstChild = reconcileChildrenIteratable(returnFiber,currentFirstChild,newChild,lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}// 处理异步可迭代类型的子节点if (enableAsyncIterableChildren &&typeof newChild[ASYNC_ITERATOR] === 'function') {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const firstChild = reconcileChildrenAsyncIteratable(returnFiber,currentFirstChild,newChild,lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}// 处理 Promise 类型的子节点if (typeof newChild.then === 'function') {const thenable: Thenable<any> = (newChild: any);// const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo);const firstChild = reconcileChildFibersImpl(returnFiber,currentFirstChild,unwrapThenable(thenable),lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}// 处理 React Context 类型if (newChild.$$typeof === REACT_CONTEXT_TYPE) {const context: ReactContext<mixed> = (newChild: any);return reconcileChildFibersImpl(returnFiber,currentFirstChild,readContextDuringReconciliation(returnFiber, context, lanes),lanes,);}// throwOnInvalidObjectType(returnFiber, newChild);}// 处理字符串、数字和大整数类型的子节点if ((typeof newChild === 'string' && newChild !== '') ||typeof newChild === 'number' ||typeof newChild === 'bigint') {return placeSingleChild(reconcileSingleTextNode(returnFiber,currentFirstChild,// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint'' + newChild,lanes,),);}// Remaining cases are all treated as empty.// 对于其他未处理的情况,将其视为空节点,调用 deleteRemainingChildren 函数删除当前父节点的所有剩余子节点,并返回 null。return deleteRemainingChildren(returnFiber, currentFirstChild);}
情况1:新节点是object类型且不为null
1-1 检查newChild.$$typeof为REACT_ELEMENT_TYPE
case REACT_ELEMENT_TYPE: {
// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const firstChild = placeSingleChild(reconcileSingleElement(returnFiber,currentFirstChild,newChild,lanes,),);// currentDebugInfo = prevDebugInfo;return firstChild;
}
reconcileSingleElement
reconcileSingleElement 函数主要是将一个新的 React 元素与现有的子 Fiber 节点进行比较,根据比较结果决定是复用现有的 Fiber 节点,还是创建一个新的 Fiber 节点。
处理步骤:
1、while循环遍历fiber的子节点,下一个处理的节点是fiber.sibling。
2、检查新旧fiber的key 。
- key相同:
- 如果是fragment,删除当前子 Fiber 节点之后的所有兄弟节点,复用其子节点,返回。
- 如果是lazy:删除当前子 Fiber 节点之后的所有兄弟节点,复用,处理ref,返回。
- 其他:删除当前子 Fiber 节点及其之后的所有兄弟节点,然后跳出循环。
- key不同:删除当前子 Fiber 节点及其之后的所有兄弟节点,然后跳出循环。
3、若新元素是 Fragment 类型,调用 createFiberFromFragment 函数创建新的 Fiber 节点,设置其 return 属性指向父 Fiber 节点,最后返回该节点。
4、若不是 Fragment 类型,调用 createFiberFromElement 函数创建新的 Fiber 节点,处理 ref,设置其 return 属性指向父 Fiber 节点,最后返回该节点。
function reconcileSingleElement(returnFiber: Fiber,// 当前元素的父 Fiber 节点。currentFirstChild: Fiber | null,// 当前的第一个子 Fiber 节点,如果没有则为 null。element: ReactElement,// 要调和的 React 元素。即新元素lanes: Lanes,// 渲染优先级车道。
): Fiber {// React 元素的 key 属性,key 用于帮助 React 识别哪些元素发生了变化,提高调和效率。const key = element.key;// 用于遍历当前的子 Fiber 节点。let child = currentFirstChild;while (child !== null) {// the first item in the list.// 当前子 Fiber 节点的 key 与新元素的 key 相同,继续检查元素类型。if (child.key === key) {const elementType = element.type;// 如果新元素是fragment类型if (elementType === REACT_FRAGMENT_TYPE) {if (child.tag === Fragment) {// 省略代码,看后续代码片段return existing;}} else {// 懒加载组件if (child.elementType === elementType ||(typeof elementType === 'object' &&elementType !== null &&elementType.$$typeof === REACT_LAZY_TYPE &&resolveLazy(elementType) === child.type)) {// 省略代码,看代码片段return existing;}}// Didn't match.deleteRemainingChildren(returnFiber, child);// 跳出循环break;} else {// key 不匹配,标记删除deleteChild(returnFiber, child);}//移动到下一个子 Fiber 节点child = child.sibling;}if (element.type === REACT_FRAGMENT_TYPE) {// 创建新的 Fiber 节点const created = createFiberFromFragment(element.props.children,returnFiber.mode,lanes,element.key,);created.return = returnFiber;// validateFragmentProps(element, created, returnFiber);return created;} else {// 创建新的 Fiber 节点const created = createFiberFromElement(element, returnFiber.mode, lanes);coerceRef(created, element);created.return = returnFiber;return created;}
}
代码片段:
if (child.tag === Fragment) {// 标记要删除child的所有兄弟节点deleteRemainingChildren(returnFiber, child.sibling);// 复用fragment下的子节点const existing = useFiber(child, element.props.children);//设置其return属性即父节点existing.return = returnFiber;// validateFragmentProps(element, existing, returnFiber);return existing;
}
为什么child.tag = Fragment
,要删除 child 的所有兄弟节点?
Fragment
是 React 里用于将多个子元素分组但又不引入额外 DOM 节点的一种方式。它允许你在不添加多余父元素的情况下返回多个元素。
从结构层面来看,Fragment
可以当作一个逻辑上的容器,它本身不会在 DOM 树中产生额外节点。所以在 Fiber
树里,Fragment
节点期望能直接包含其子元素,而不应该有其他兄弟节点与其并列。
Fiber
调和过程的目的是找出新旧 Fiber
树之间的差异,然后最小化 DOM 操作来更新页面。当遇到 Fragment
节点时,React 会认为 Fragment
应该是当前层级下的唯一逻辑容器。如果 Fragment
有兄弟节点,可能会打乱这种逻辑结构,使得调和过程变得复杂。
代码片段:
if (// 检查当前 child 节点的 elementType 是否与新元素的 elementType 相同。elementType 通常表示元素的类型,比如函数组件、类组件或者 HTML 标签名等。如果相同,说明可能可以复用这个节点。child.elementType === elementType ||// 懒加载组件(typeof elementType === 'object' &&elementType !== null &&elementType.$$typeof === REACT_LAZY_TYPE &&resolveLazy(elementType) === child.type)) {// 标记删除所有的兄弟节点deleteRemainingChildren(returnFiber, child.sibling);// 复用child节点const existing = useFiber(child, element.props);// 处理refcoerceRef(existing, element);existing.return = returnFiber;return existing;
}
createFiberFromElement
createFiberFromElement
函数的主要功能是依据传入的 React 元素(ReactElement
)创建一个对应的 Fiber
节点。
函数参数说明:
element: ReactElement
:待转换的 React 元素,它包含了组件的类型、属性、key
等信息。mode: TypeOfMode
:节点的模式,用于控制该节点及其子节点的行为,像是否启用并发模式等。lanes: Lanes
:渲染优先级车道,用来确定该节点的渲染优先级。
function createFiberFromElement(element: ReactElement,mode: TypeOfMode,lanes: Lanes,
): Fiber {// 初始ownerlet owner = null;// 获取 React 元素的类型,可能是函数组件、类组件或者原生 DOM 元素类型。const type = element.type;// 获取 React 元素的 key 属性,key 用于辅助 React 识别哪些元素发生了变化,提升调和效率。const key = element.key;// 获取 React 元素的属性,也就是传递给组件的新属性。const pendingProps = element.props;const fiber = createFiberFromTypeAndProps(type,// 元素类型key,pendingProps,owner,mode,// 模式,并发模式lanes,// 渲染优先级车道);return fiber;
}
createFiberFromTypeAndProps
createFiberFromTypeAndProps
函数的主要功能是根据传入的组件类型、属性等信息,创建相应类型的 Fiber
节点。在 React 的 Fiber
架构中,Fiber
节点是协调和渲染过程的核心数据结构,该函数会根据不同的组件类型(如函数组件、类组件、各种特殊组件等)设置 Fiber
节点的属性和类型标签,最终返回创建好的 Fiber
节点,为后续的渲染和更新操作提供基础。
函数参数说明
type: any
:React 组件的类型,可以是函数、类、字符串(代表原生 DOM 元素)或特殊的 React 类型(如Fragment
、Context
等)。key: null | string
:节点的key
属性,用于帮助 React 识别哪些元素发生了变化,提高调和效率。pendingProps: any
:组件的待处理属性。owner: null | ReactComponentInfo | Fiber
:创建该组件的所有者,可能是null
、ReactComponentInfo
或Fiber
节点。mode: TypeOfMode
:节点的模式,用于控制该节点及其子节点的行为,例如是否启用并发模式等。lanes: Lanes
:渲染优先级车道,用于确定该节点的渲染优先级。
function createFiberFromTypeAndProps(type: any, // React$ElementTypekey: null | string,pendingProps: any,owner: null | ReactComponentInfo | Fiber,mode: TypeOfMode,lanes: Lanes,
): Fiber {// fiberTag 初始化为 FunctionComponent,表示默认情况下将组件视为函数组件。// fiberTag代表节点类型let fiberTag = FunctionComponent;// 存储组件类型let resolvedType = type;if (typeof type === 'function') {// 如果 type 是函数类型,调用 shouldConstruct 函数判断该函数是否为类组件的构造函数。如果是,则将 fiberTag 设置为 ClassComponent。if (shouldConstruct(type)) {fiberTag = ClassComponent;} } else if (typeof type === 'string') {// 省略代码。。。看后面的代码片段分析} else {// 创建不同类型的fiber节点getTag: switch (type) {// 当 type 为 REACT_FRAGMENT_TYPE 时,表示当前元素是一个 Fragment。Fragment 是 React 中用于分组多个子元素而不引入额外 DOM 节点的方式。case REACT_FRAGMENT_TYPE:return createFiberFromFragment(pendingProps.children, mode, lanes, key);// 当 type 为 REACT_STRICT_MODE_TYPE 时,表示当前元素是一个 StrictMode 组件。StrictMode 用于在开发模式下帮助发现潜在的问题,如不安全的生命周期方法、过时的 API 使用等。case REACT_STRICT_MODE_TYPE:fiberTag = Mode;mode |= StrictLegacyMode;if (disableLegacyMode || (mode & ConcurrentMode) !== NoMode) {// Strict effects should never run on legacy rootsmode |= StrictEffectsMode;if (enableDO_NOT_USE_disableStrictPassiveEffect &&pendingProps.DO_NOT_USE_disableStrictPassiveEffect) {mode |= NoStrictPassiveEffectsMode;}}break;// 当 type 为 REACT_PROFILER_TYPE 时,表示当前元素是一个 Profiler 组件。Profiler 用于测量 React 应用的性能,记录组件渲染的时间。case REACT_PROFILER_TYPE:return createFiberFromProfiler(pendingProps, mode, lanes, key);// :当 type 为 REACT_SUSPENSE_TYPE 时,表示当前元素是一个 Suspense 组件。Suspense 用于处理异步加载的组件,在组件加载完成之前显示一个加载指示器。case REACT_SUSPENSE_TYPE:return createFiberFromSuspense(pendingProps, mode, lanes, key);// 省略一些代码。。// Fall throughdefault: {if (typeof type === 'object' && type !== null) {switch (type.$$typeof) {// 当 type.$$typeof 为 REACT_PROVIDER_TYPE 时,表明这是一个 Context.Provider 组件。case REACT_PROVIDER_TYPE:if (!enableRenderableContext) {fiberTag = ContextProvider;break getTag;}// Fall through// 当 type.$$typeof 为 REACT_CONTEXT_TYPE 时,代表这是一个 Context 对象。 case REACT_CONTEXT_TYPE:if (enableRenderableContext) {fiberTag = ContextProvider;break getTag;} else {fiberTag = ContextConsumer;break getTag;}
// 当 type.$$typeof 为 REACT_CONSUMER_TYPE 时,说明这是一个 Context.Consumer 组件。case REACT_CONSUMER_TYPE:if (enableRenderableContext) {fiberTag = ContextConsumer;break getTag;}// Fall through
// 当 type.$$typeof 为 REACT_FORWARD_REF_TYPE 时,表明这是一个使用 React.forwardRef 创建的组件。case REACT_FORWARD_REF_TYPE:fiberTag = ForwardRef;break getTag;// 当 type.$$typeof 为 REACT_MEMO_TYPE 时,意味着这是一个使用 React.memo 包装的组件。 case REACT_MEMO_TYPE:fiberTag = MemoComponent;break getTag;// 当 type.$$typeof 为 REACT_LAZY_TYPE 时,表示这是一个使用 React.lazy 创建的懒加载组件。case REACT_LAZY_TYPE:fiberTag = LazyComponent;resolvedType = null;break getTag;}}let info = '';let typeString;typeString = type === null ? 'null' : typeof type;// 如果以上都不是,fiberTag表示错误组件fiberTag = Throw;pendingProps = new Error('Element type is invalid: expected a string (for built-in ' +'components) or a class/function (for composite components) ' +`but got: ${typeString}.${info}`,);resolvedType = null;}}}// 创建fiberconst fiber = createFiber(fiberTag, pendingProps, key, mode);fiber.elementType = type;fiber.type = resolvedType;fiber.lanes = lanes;return fiber;
}
可以看出组件lazy、memo、forwardRef、Provide、Consumer组件都是使用createFiber创建相应的fiber节点。
代码片段:
if (typeof type === 'string') {if (supportsResources && supportsSingletons) {// 获取宿主环境的上下文信息,这可能包含了一些与渲染环境相关的数据。const hostContext = getHostContext();// isHostHoistableType函数判断该元素是否为可提升的宿主类型fiberTag = isHostHoistableType(type, pendingProps, hostContext)? HostHoistable// meta,title等: isHostSingletonType(type)? HostSingleton : HostComponent;} else if (supportsResources) {const hostContext = getHostContext();fiberTag = isHostHoistableType(type, pendingProps, hostContext)? HostHoistable: HostComponent;} else if (supportsSingletons) {fiberTag = isHostSingletonType(type) ? HostSingleton : HostComponent;} else {fiberTag = HostComponent;}
}
例子:createContext的使用(Provider 和 Consumer)
import { createContext } from 'react';const ThemeContext = createContext('light');function App() {const [theme, setTheme] = useState('light');// ...return (<ThemeContext.Provider value={theme}><Page /></ThemeContext.Provider>);
}
function Button() {// 🟡 Legacy way (not recommended)return (<ThemeContext.Consumer>{theme => (<button className={theme} />)}</ThemeContext.Consumer>);
}
createFiberFromFragment
createFiberFromFragment
函数用于创建一个表示 React Fragment 的 Fiber 节点。它通过调用 createFiber
函数初始化基本的 Fiber 结构,并设置其 lanes
属性以标记该 Fiber 的优先级。Fragment 是一种特殊的 React 元素,用于将多个子元素分组而不引入额外的 DOM 节点。
函数参数说明:
Fragment
:指定 Fiber 类型为 Fragment(对应 React 内部的Fragment
标签类型)。elements
:Fragment 包含的子元素集合,通常是一个数组。key
:Fragment 的 key 值,用于在协调过程中识别元素。mode
:Fiber 的模式,决定了渲染行为(如并发模式、严格模式等)。
function createFiberFromFragment(elements: ReactFragment,mode: TypeOfMode,lanes: Lanes,key: null | string,
): Fiber {
// Fragment常量 为7const fiber = createFiber(Fragment, elements, key, mode);fiber.lanes = lanes;return fiber;
}
createFiberFromSuspense
createFiberFromSuspense
函数用于创建一个表示 React Suspense 组件的 Fiber 节点。Suspense 是 React 中用于处理异步加载内容的特性,允许组件在等待数据时显示加载状态。该函数初始化了 Suspense 组件的基本 Fiber 结构,并设置了相关属性。
参数说明:
SuspenseComponent
:指定 Fiber 类型为 Suspense 组件(对应 React 内部的 Suspense 标签类型)。pendingProps
:Suspense 组件的属性,通常包含fallback
(加载状态内容)和子组件。key
:Suspense 组件的 key 值,用于在协调过程中识别元素。mode
:Fiber 的模式,决定了渲染行为(如并发模式、严格模式等)。
function createFiberFromSuspense(pendingProps: any,mode: TypeOfMode,lanes: Lanes,key: null | string,
): Fiber {const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);fiber.elementType = REACT_SUSPENSE_TYPE;fiber.lanes = lanes;return fiber;
}
1-2 检查newChild.$$typeof为REACT_PORTAL_TYPE
- 遍历现有的子 Fiber 节点。
- 检查新旧节点的key是否相同。
- key相同:
- 可复用:删除当前子 Fiber 节点之后的所有兄弟节点。复用当前子 Fiber 节点,并更新其 children 属性。复用节点的 return 属性指向父 Fiber 节点,返回复用节点。
- 不可复用:删除当前子 Fiber 节点之后的所有兄弟节点,并跳出循环。
- key不同:删除当前子 Fiber 节点之后的所有兄弟节点。
- key相同:
- 结束循环。
- 创建新的 Fiber 节点并返回。
// 处理 React Portal 类型case REACT_PORTAL_TYPE:return placeSingleChild(reconcileSinglePortal(returnFiber,currentFirstChild,newChild,lanes,),);
reconcileSinglePortal
reconcileSinglePortal
函数的主要作用是调和单个 ReactPortal
元素。在 React 中,Portal
是一种将子节点渲染到 DOM 树中当前组件层次结构之外位置的方式。该函数会尝试复用现有的 Fiber
节点,如果无法复用则创建一个新的 Fiber
节点,同时处理相关的兄弟节点删除操作。
处理过程:
1、遍历现有的子 Fiber 节点
2、检查新旧节点的key是否相同
- key相同:
- 可复用:删除当前子 Fiber 节点之后的所有兄弟节点。复用当前子 Fiber 节点,并更新其 children 属性。复用节点的 return 属性指向父 Fiber 节点。返回复用节点
- 不可复用:删除当前子 Fiber 节点之后的所有兄弟节点,并跳出循环。
- key不同:删除当前子 Fiber 节点之后的所有兄弟节点。
2、结束循环。
3、创建新的 Fiber 节点,并返回
function reconcileSinglePortal(returnFiber: Fiber,//:父 Fiber 节点,即当前 Portal 元素所属的父节点。currentFirstChild: Fiber | null,// 当前父节点的第一个子 Fiber 节点,可能为 null。portal: ReactPortal,// 新的 ReactPortal 元素,包含了要渲染的子节点以及目标容器信息。lanes: Lanes,// 渲染优先级车道,用于表示当前操作的优先级。
): Fiber {const key = portal.key;let child = currentFirstChild;while (child !== null) {if (child.key === key) {if (child.tag === HostPortal &&// 检查子节点的目标容器信息是否与 Portal 元素的目标容器信息相同。child.stateNode.containerInfo === portal.containerInfo &&// 检查子节点的实现方式是否与 Portal 元素的实现方式相同。child.stateNode.implementation === portal.implementation) {deleteRemainingChildren(returnFiber, child.sibling);const existing = useFiber(child, portal.children || []);existing.return = returnFiber;return existing;} else {deleteRemainingChildren(returnFiber, child);break;}} else {deleteChild(returnFiber, child);}// 将 child 指针指向当前子节点的下一个兄弟节点,继续遍历。child = child.sibling;}const created = createFiberFromPortal(portal, returnFiber.mode, lanes);created.return = returnFiber;return created;
}
createFiberFromPortal
createFiberFromPortal
函数的主要功能是依据给定的 ReactPortal
对象、模式和优先级车道,创建一个 HostPortal
类型的 Fiber
节点。ReactPortal
是 React 提供的一种特殊机制,它能让子组件渲染到 DOM 树中当前组件层次结构之外的位置。此函数会为 Portal
创建对应的 Fiber
节点,以便在 Fiber
架构里对其进行调和与渲染。
function createFiberFromPortal(portal: ReactPortal,mode: TypeOfMode,lanes: Lanes,
): Fiber {const pendingProps = portal.children !== null ? portal.children : [];const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);fiber.lanes = lanes;fiber.stateNode = {containerInfo: portal.containerInfo,pendingChildren: null, // Used by persistent updatesimplementation: portal.implementation,};return fiber;
}
1-3 检查newChild.$$typeof为REACT_LAZY_TYPE
// 处理 React Lazy 类型case REACT_LAZY_TYPE: {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);let result;// newChild._payload 是传递给初始化函数的数据const payload = newChild._payload;// newChild._init 是懒加载组件的初始化函数。const init = newChild._init;// 调用初始化函数 init 并传入 payload,以获取实际的组件。通常,init 函数会动态导入组件并返回。result = init(payload);// reconcileChildFibersImpl:这是一个内部函数,用于调和子 Fiber 节点。它会比较新旧 Fiber 树,找出差异并更新 Fiber 树。const firstChild = reconcileChildFibersImpl(returnFiber,currentFirstChild,result,lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}
1-4 检查newChild为数组类型(多个同级子节点)
// 处理数组类型的子节点if (isArray(newChild)) {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const firstChild = reconcileChildrenArray(returnFiber,currentFirstChild,newChild,lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}
reconcileChildrenArray
reconcileChildrenArray
函数是用于对比新旧子节点数组,找出它们之间的差异,然后根据这些差异更新 Fiber
树,以反映新的 UI 状态。它会对新旧子节点进行遍历和比较,执行插入、删除、移动等操作,并标记相应的副作用,最终返回新的第一个子 Fiber
节点。
处理过程:
- 第一轮遍历:同步比较新旧子节点
- 如果新子节点遍历完,删除剩余的旧子节点,返回,结束。
- 如果旧子节点遍历完,创建剩余的新子节点。并将剩余的旧子节点存储到一个 Map 中
- 第二轮遍历:处理剩余的新子节点
- 删除剩余未使用的旧子节点
function reconcileChildrenArray(returnFiber: Fiber,// 父节点currentFirstChild: Fiber | null,// newChildren: Array<any>,lanes: Lanes,): Fiber | null {// 用于存储已知的键集合,初始为 null。let knownKeys: Set<string> | null = null;// 存储新的第一个子 Fiber 节点,初始为 null。let resultingFirstChild: Fiber | null = null;// 存储上一个处理的新 Fiber 节点,初始为 null。let previousNewFiber: Fiber | null = null;// 指向当前旧的子 Fiber 节点,初始为 currentFirstChild。let oldFiber = currentFirstChild;// 记录最后一个被放置的节点的索引,初始为 0。let lastPlacedIndex = 0;// 用于遍历新子节点数组的索引,初始为 0。let newIdx = 0;// 存储下一个旧的子 Fiber 节点,初始为 null。let nextOldFiber = null;// 第一轮遍历:同步遍历新旧子节点for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {if (oldFiber.index > newIdx) {nextOldFiber = oldFiber;oldFiber = null;} else {nextOldFiber = oldFiber.sibling;}// updateSlot 函数用于尝试复用旧的 Fiber 节点或创建新的 Fiber 节点。const newFiber = updateSlot(returnFiber,oldFiber,newChildren[newIdx],lanes,);// 如果 newFiber 为 null,说明当前位置没有匹配的节点,跳出循环。if (newFiber === null) {if (oldFiber === null) {oldFiber = nextOldFiber;}break;}// 需要跟踪副作用且旧的 Fiber 节点未被复用,则调用 deleteChild 函数删除旧的子节点。if (shouldTrackSideEffects) {if (oldFiber && newFiber.alternate === null) {deleteChild(returnFiber, oldFiber);}}// placeChild 函数用于确定新 Fiber 节点的插入位置,并更新 lastPlacedIndex。lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber === null) {// TODO: Move out of the loop. This only happens for the first run.resultingFirstChild = newFiber;} else {previousNewFiber.sibling = newFiber;}previousNewFiber = newFiber;oldFiber = nextOldFiber;}// 处理新子节点遍历完的情况if (newIdx === newChildren.length) {deleteRemainingChildren(returnFiber, oldFiber);return resultingFirstChild;}// 处理旧子节点遍历完的情况if (oldFiber === null) {for (; newIdx < newChildren.length; newIdx++) {const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);if (newFiber === null) {continue;}lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber === null) {resultingFirstChild = newFiber;} else {previousNewFiber.sibling = newFiber;}previousNewFiber = newFiber;}return resultingFirstChild;}// Add all children to a key map for quick lookups.// 处理剩余的新旧子节点const existingChildren = mapRemainingChildren(oldFiber);// Keep scanning and use the map to restore deleted items as moves.for (; newIdx < newChildren.length; newIdx++) {const newFiber = updateFromMap(existingChildren,returnFiber,newIdx,newChildren[newIdx],lanes,);if (newFiber !== null) {if (shouldTrackSideEffects) {if (newFiber.alternate !== null) {existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key,);}}lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber === null) {resultingFirstChild = newFiber;} else {previousNewFiber.sibling = newFiber;}previousNewFiber = newFiber;}}// 删除剩余的旧子节点if (shouldTrackSideEffects) {existingChildren.forEach(child => deleteChild(returnFiber, child));}// if (getIsHydrating()) {// const numberOfForks = newIdx;// pushTreeFork(returnFiber, numberOfForks);// }// 返回新的第一个子 Fiber 节点return resultingFirstChild;}
1-5 检查newChild为可迭代类型
// 处理可迭代类型的子节点if (getIteratorFn(newChild)) {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const firstChild = reconcileChildrenIteratable(returnFiber,currentFirstChild,newChild,lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}
reconcileChildrenIterator
reconcileChildrenIterator
函数的主要功能是对新旧子节点进行协调,以找出它们之间的差异,并根据这些差异更新 Fiber
树。该函数接收一个迭代器 newChildren
作为新的子节点来源,会将其与旧的子 Fiber
节点进行比较,然后执行插入、删除、移动等操作,最终返回新的第一个子 Fiber
节点。
函数参数含义
returnFiber
:类型为Fiber
,代表父Fiber
节点,即当前处理的子节点所属的父节点。currentFirstChild
:类型为Fiber | null
,表示当前父Fiber
节点的第一个子Fiber
节点,如果没有子节点则为null
。newChildren
:类型为?Iterator<mixed>
,是一个迭代器对象,用于遍历新的子节点。lanes
:类型为Lanes
,表示渲染优先级,用于确定此次更新的优先级。
处理过程:
1、同步遍历新旧节点。
2、处理新子节点遍历完的情况。如果完毕这里结束。
3、处理旧子节点遍历完的情况。剩余新子节点新建。
4、处理剩余的新旧子节点,依据剩余新子节点查询就节点map中可复用的。
5、删除剩余的旧子节点。
6、返回新的第一个子 Fiber 节点。
function reconcileChildrenIterator(returnFiber: Fiber,currentFirstChild: Fiber | null,newChildren: ?Iterator<mixed>,lanes: Lanes,): Fiber | null {if (newChildren == null) {throw new Error('An iterable object provided no iterator.');}// 存储新fiber的第一个子节点let resultingFirstChild: Fiber | null = null;// 存储上一个处理的新 Fiber 节点,用于构建新的 Fiber 节点链表。let previousNewFiber: Fiber | null = null;// 旧fiberlet oldFiber = currentFirstChild;// 记录最后一个放置的 Fiber 节点的索引,用于判断节点是否需要移动。let lastPlacedIndex = 0;let newIdx = 0;let nextOldFiber = null;// 下一个fiber节点// 用于存储已知的 key,初始为 null。let knownKeys: Set<string> | null = null;// 下一个新子节点let step = newChildren.next();// 1、同步遍历新旧节点for (;oldFiber !== null && !step.done;newIdx++, step = newChildren.next()) {if (oldFiber.index > newIdx) {nextOldFiber = oldFiber;oldFiber = null;} else {nextOldFiber = oldFiber.sibling;}const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);if (newFiber === null) {if (oldFiber === null) {oldFiber = nextOldFiber;}break;}lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber === null) {resultingFirstChild = newFiber;} else {previousNewFiber.sibling = newFiber;}previousNewFiber = newFiber;oldFiber = nextOldFiber;}// 2、处理新子节点遍历完的情况if (step.done) {deleteRemainingChildren(returnFiber, oldFiber);return resultingFirstChild;}// 3、处理旧子节点遍历完的情况if (oldFiber === null) {for (; !step.done; newIdx++, step = newChildren.next()) {const newFiber = createChild(returnFiber, step.value, lanes);if (newFiber === null) {continue;}lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber === null) {// TODO: Move out of the loop. This only happens for the first run.resultingFirstChild = newFiber;} else {previousNewFiber.sibling = newFiber;}previousNewFiber = newFiber;}return resultingFirstChild;}// 4、处理剩余的新旧子节点// Add all children to a key map for quick lookups.const existingChildren = mapRemainingChildren(oldFiber);// Keep scanning and use the map to restore deleted items as moves.for (; !step.done; newIdx++, step = newChildren.next()) {const newFiber = updateFromMap(existingChildren,returnFiber,newIdx,step.value,lanes,);if (newFiber !== null) {if (shouldTrackSideEffects) {if (newFiber.alternate !== null) {existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key,);}}lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber === null) {resultingFirstChild = newFiber;} else {previousNewFiber.sibling = newFiber;}previousNewFiber = newFiber;}}// 5、删除剩余的旧子节点if (shouldTrackSideEffects) {existingChildren.forEach(child => deleteChild(returnFiber, child));}return resultingFirstChild;}
1-6 检查newChild为异步可迭代类型
// 处理异步可迭代类型的子节点if (enableAsyncIterableChildren &&typeof newChild[ASYNC_ITERATOR] === 'function') {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const firstChild = reconcileChildrenAsyncIteratable(returnFiber,currentFirstChild,newChild,lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}
reconcileChildrenAsyncIteratable
reconcileChildrenAsyncIteratable
函数主要用于处理异步可迭代的子节点。在 React 的协调过程中,当遇到异步可迭代的子节点时,该函数会对其进行处理,最终调用 reconcileChildrenIterator
函数完成子节点的协调工作。
函数参数含义
returnFiber
:类型为Fiber
,代表父Fiber
节点,即当前处理的子节点所属的父节点。currentFirstChild
:类型为Fiber | null
,表示当前父Fiber
节点的第一个子Fiber
节点,如果没有子节点则为null
。newChildrenIterable
:类型为AsyncIterable<mixed>
,是一个异步可迭代对象,包含了新的子节点数据。lanes
:类型为Lanes
,表示渲染优先级,用于确定此次更新的优先级。
function reconcileChildrenAsyncIteratable(returnFiber: Fiber,currentFirstChild: Fiber | null,newChildrenIterable: AsyncIterable<mixed>,lanes: Lanes,
): Fiber | null {// 获取异步迭代器const newChildren = newChildrenIterable[ASYNC_ITERATOR]();if (newChildren == null) {throw new Error('An iterable object provided no iterator.');}// 创建同步迭代器const iterator: Iterator<mixed> = ({next(): IteratorResult<mixed, void> {return unwrapThenable(newChildren.next());},}: any);return reconcileChildrenIterator(returnFiber,currentFirstChild,iterator,// 同步迭代器lanes,);
}
1-7 检查newChild为Promise类型
// 处理 Promise 类型的子节点if (typeof newChild.then === 'function') {const thenable: Thenable<any> = (newChild: any);// const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo);const firstChild = reconcileChildFibersImpl(returnFiber,currentFirstChild,unwrapThenable(thenable),lanes,);// currentDebugInfo = prevDebugInfo;return firstChild;}
1-8检查newChild.$$typeof为REACT_CONTEXT_TYPE
// 处理 React Context 类型if (newChild.$$typeof === REACT_CONTEXT_TYPE) {const context: ReactContext<mixed> = (newChild: any);return reconcileChildFibersImpl(returnFiber,currentFirstChild,readContextDuringReconciliation(returnFiber, context, lanes),lanes,);}// throwOnInvalidObjectType(returnFiber, newChild);}
工具函数之readContextDuringReconciliation
readContextDuringReconciliation
函数是会根据当前渲染状态决定是否需要初始化上下文读取环境,然后调用 readContextForConsumer
实际读取上下文值并建立依赖关系。该函数确保在协调阶段安全地读取上下文,避免因环境未准备而导致的错误。
function readContextDuringReconciliation<T>(consumer: Fiber,context: ReactContext<T>,renderLanes: Lanes,
): T {
// 当 currentlyRenderingFiber 为 null 时,表示当前没有正在渲染的 Fiber,需要初始化上下文读取环境。if (currentlyRenderingFiber === null) {prepareToReadContext(consumer, renderLanes);}return readContextForConsumer(consumer, context);
}
工具函数之prepareToReadContext
prepareToReadContext
函数是 React 渲染流程中的关键环节,用于为当前 Fiber 节点准备上下文读取环境。它主要完成以下工作:
- 设置当前渲染的 Fiber 节点。
- 重置上下文依赖链表。
- 处理上下文更新标记,确保依赖的上下文变化能触发组件重新渲染。
function prepareToReadContext(workInProgress: Fiber,renderLanes: Lanes,
): void {currentlyRenderingFiber = workInProgress;lastContextDependency = null;const dependencies = workInProgress.dependencies;// 处理已有的上下文依赖if (dependencies !== null) {// 启用传播模式if (enableLazyContextPropagation) {// Reset the work-in-progress listdependencies.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;}}}
}
工具函数之readContextForConsumer
readContextForConsumer
函数是 React 中用于读取上下文(Context)值的核心内部函数,主要用于类组件和函数组件中消费上下文。它会从当前上下文对象中获取最新值,并建立上下文依赖关系,确保当上下文值变化时,组件能正确触发重新渲染。
function readContextForConsumer<C>(consumer: Fiber | null,context: ReactContext<C>,
): C {// 根据当前渲染器类型(主渲染器或辅助渲染器),从上下文对象(context)中获取对应的值。const value = isPrimaryRenderer? context._currentValue: context._currentValue2;// 创建上下文依赖项const contextItem = {context: ((context: any): ReactContext<mixed>),memoizedValue: value,next: null,};// 当 lastContextDependency 为 null 时,说明组件首次读取上下文,创建新的依赖链表。if (lastContextDependency === null) {// 抛出错误:上下文只能在渲染阶段读取if (consumer === null) {throw new Error('Context can only be read while React is rendering. ' +'In classes, you can read it in the render method or getDerivedStateFromProps. ' +'In function components, you can read it directly in the function body, but not ' +'inside Hooks like useReducer() or useMemo().',);}// This is the first dependency for this component. Create a new list.// 初始化依赖链表lastContextDependency = contextItem;consumer.dependencies = {lanes: NoLanes,firstContext: contextItem,};// 启用延迟上下文传播// if (enableLazyContextPropagation) {// consumer.flags |= NeedsPropagation;// }} else {// 非首次依赖:将新的 contextItem 追加到链表末尾,形成链式结构(next 指针串联所有依赖项)。// Append a new context item.lastContextDependency = lastContextDependency.next = contextItem;}return value;
}
情况2:新节点为string类型 且不为空 或新节点为number类型或者bigint类型
if ((typeof newChild === 'string' && newChild !== '') ||typeof newChild === 'number' ||typeof newChild === 'bigint') {return placeSingleChild(reconcileSingleTextNode(returnFiber,currentFirstChild,// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint'' + newChild,lanes,),);}
reconcileSingleTextNode
reconcileSingleTextNode
函数的主要功能是对单个文本节点进行协调(调和)操作。在 React 的协调算法中,该函数会对比现有的子 Fiber
节点和新的文本内容,尝试复用现有的文本 Fiber
节点,若无法复用则创建新的文本 Fiber
节点,以此确保 Fiber
树能够准确反映最新的文本内容。
函数参数含义
returnFiber
:类型为Fiber
,代表当前文本节点的父Fiber
节点,即新文本节点要插入到的父节点。currentFirstChild
:类型为Fiber | null
,表示当前父Fiber
节点的第一个子Fiber
节点。若不存在子节点,该值为null
。textContent
:类型为string
,是新的文本内容。lanes
:类型为Lanes
,代表渲染优先级车道,用于确定此次更新的优先级。
function reconcileSingleTextNode(returnFiber: Fiber,//父节点currentFirstChild: Fiber | null,// 第一个子节点textContent: string,lanes: Lanes,
): Fiber {// 检查是否有可复用的文本 Fiber 节点if (currentFirstChild !== null && currentFirstChild.tag === HostText) {deleteRemainingChildren(returnFiber, currentFirstChild.sibling);// 复用节点const existing = useFiber(currentFirstChild, textContent);existing.return = returnFiber;return existing;}deleteRemainingChildren(returnFiber, currentFirstChild);const created = createFiberFromText(textContent, returnFiber.mode, lanes);created.return = returnFiber;return created;
}
createFiberFromText
createFiberFromText
函数的主要功能是依据给定的文本内容、模式和优先级车道,创建一个代表文本节点的 Fiber
节点。在 React 的 Fiber
架构里,Fiber
节点是构建虚拟 DOM 树的基本单元,此函数专门用于创建文本类型的 Fiber
节点。
函数参数含义:
content
:类型为string
,代表要创建的文本节点的具体内容。mode
:类型为TypeOfMode
,表示Fiber
节点的模式,该模式会影响Fiber
节点在协调过程中的行为。lanes
:类型为Lanes
,表示渲染优先级,用于确定该Fiber
节点的更新优先级。
function createFiberFromText(content: string,mode: TypeOfMode,// 并发模式,传统模式lanes: Lanes,
): Fiber {// HostText常量6,表示该 Fiber 节点的类型为文本节点。// 文本节点通常没有 key,所以这里传入 null。key 一般用于在列表中唯一标识元素,帮助 React 识别哪些元素发生了变化。const fiber = createFiber(HostText, content, null, mode);fiber.lanes = lanes;return fiber;
}
情况3:其他
return deleteRemainingChildren(returnFiber, currentFirstChild);
deleteRemainingChildren
用于删除从指定 Fiber
节点开始的所有剩余子 Fiber
节点。它会根据是否需要跟踪副作用来决定是否执行删除操作,若需要跟踪,则会逐个删除子节点。
function deleteRemainingChildren(returnFiber: Fiber,// 父节点fibercurrentFirstChild: Fiber | null,// 要删除的第一个子节点
): null {// 不需要跟踪副作用,,直接跳出if (!shouldTrackSideEffects) {// Noop.return null;}// 初始化 childToDelete 为 currentFirstChild,即从第一个子节点开始处理。let childToDelete = currentFirstChild;while (childToDelete !== null) {// 调用 deleteChild 函数来标记删除当前的子节点。deleteChild(returnFiber, childToDelete);// 将 childToDelete 更新为其下一个兄弟节点(通过 childToDelete.sibling),继续处理下一个子节点。childToDelete = childToDelete.sibling;}return null;
}
deleteChild
deleteChild
函数的主要功能是标记一个 Fiber
节点为待删除状态。在 React 的 Fiber
架构中,删除操作不会立即执行,而是先标记需要删除的节点,等到提交阶段(commit phase)再统一处理。这样做的好处是可以批量处理删除操作,减少对 DOM 的频繁修改,提高性能。
function deleteChild(returnFiber: Fiber,// 父节点childToDelete: Fiber// 需要删除的子节点
): void {// shouldTrackSideEffects 是一个布尔值,用于控制是否需要跟踪副作用。副作用在 React 中通常指的是对 DOM 进行的插入、删除、更新等操作。如果不需要跟踪副作用,函数直接返回,不进行任何操作。这是一种优化机制,避免在不需要记录删除操作时进行额外的处理。if (!shouldTrackSideEffects) {// Noop.return;}// returnFiber.deletions 是一个数组,用于存储该父节点下需要删除的子 Fiber 节点。通过将需要删除的子节点存储在这个数组中,React 可以在后续的提交阶段一次性处理这些删除操作。const deletions = returnFiber.deletions;if (deletions === null) {returnFiber.deletions = [childToDelete];// ChildDeletion 标记表示该父节点有子节点需要被删除。returnFiber.flags |= ChildDeletion;} else {// 将 childToDelete 添加到 deletions 数组中,以便后续统一处理。deletions.push(childToDelete);}
}
工具函数之 placeSingleChild
placeSingleChild
函数主要用于为单个新的 Fiber
节点标记插入副作用。在 React 的协调过程中,需要确定哪些 Fiber
节点需要进行插入操作,这个函数会根据特定条件判断新的 Fiber
节点是否需要被标记为插入,并设置相应的 flags
。
function placeSingleChild(newFiber: Fiber): Fiber {// shouldTrackSideEffects 说明是否需要跟踪 (挂载时为false, 更新时为true)// 副作用一般指的是会对外部状态产生影响的操作,像插入、删除、更新 DOM 节点等。// newFiber.alternate为null ,说明newFiber是新创建的if (shouldTrackSideEffects && newFiber.alternate === null) {newFiber.flags |= Placement | PlacementDEV;// 代表插入}return newFiber;
}
工具函数之 useFiber
基于已有的 Fiber
节点创建一个新的 workInProgress
(进行中的工作)Fiber
节点。workInProgress
用于表示当前正在处理的 Fiber
树,通过复用旧的 Fiber
节点可以提高性能。这个函数接收一个旧的 Fiber
节点和新的 props
,然后返回一个新的 workInProgress
Fiber
节点,并且会重置该节点的索引和兄弟节点信息。
function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {const clone = createWorkInProgress(fiber, pendingProps);clone.index = 0;clone.sibling = null;return clone;
}
工具函数之 createWorkInProgress
createWorkInProgress
用于创建或复用一个 workInProgress
(进行中的工作)Fiber 节点。在 React 的渲染流程中,为了实现可中断的渲染和双缓存机制,会使用两个 Fiber 树:当前渲染的 Fiber 树(current
)和正在构建的 Fiber 树(workInProgress
)。这个函数的作用就是根据当前的 Fiber 节点(current
)和新的待处理属性(pendingProps
),创建或更新 workInProgress
节点。
参数说明:
current: Fiber
:当前正在渲染的 Fiber 节点。pendingProps: any
:新的待处理属性,用于更新workInProgress
节点。
function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {let workInProgress = current.alternate;// 如果 workInProgress 为 null,说明还没有对应的进行中的工作节点,需要创建一个新的if (workInProgress === null) {// 创建新的 workInProgress 节点workInProgress = createFiber(current.tag,// 当前节点的类型标签(tag)pendingProps,// 待处理的propscurrent.key,current.mode,);// 复制当前节点的 elementType、type 和 stateNode 到新节点。workInProgress.elementType = current.elementType;workInProgress.type = current.type;workInProgress.stateNode = current.stateNode;// 建立 workInProgress 和 current 之间的双向引用,即 workInProgress.alternate 指向 current,current.alternate 指向 workInProgress。workInProgress.alternate = current;current.alternate = workInProgress;} else {// 复用已有的 workInProgress 节点并更新属性workInProgress.pendingProps = pendingProps;// Needed because Blocks store data on type.workInProgress.type = current.type;// We already have an alternate.// Reset the effect tag.workInProgress.flags = NoFlags;// The effects are no longer valid.workInProgress.subtreeFlags = NoFlags;workInProgress.deletions = null;}// 复制部分属性到 workInProgress 节点workInProgress.flags = current.flags & StaticMask;workInProgress.childLanes = current.childLanes;workInProgress.lanes = current.lanes;workInProgress.child = current.child;workInProgress.memoizedProps = current.memoizedProps;// 上一次的渲染属性workInProgress.memoizedState = current.memoizedState;// 上一次渲染状态workInProgress.updateQueue = current.updateQueue;// 更新队列const currentDependencies = current.dependencies;// 对于依赖项,进行浅复制,避免共享引用。workInProgress.dependencies =currentDependencies === null? null: {lanes: currentDependencies.lanes,firstContext: currentDependencies.firstContext,};// These will be overridden during the parent's reconciliationworkInProgress.sibling = current.sibling;workInProgress.index = current.index;workInProgress.ref = current.ref;workInProgress.refCleanup = current.refCleanup;return workInProgress;
}
工具函数之 coerceRef
coerceRef
函数的主要作用是将 ReactElement
(即 React 元素)的 ref
属性赋值给对应的 Fiber
节点(workInProgress
)。在 React 的协调过程中,Fiber
节点是协调算法的工作单元,用于表示组件树中的一个节点,而 ref
属性通常用于获取 DOM 节点或组件实例的引用。
function coerceRef(workInProgress: Fiber, element: ReactElement): void {// 获取react元素的ref属性const refProp = element.props.ref;// should always read the ref from the prop.// 将refProp赋给当前fiberworkInProgress.ref = refProp !== undefined ? refProp : null;
}
工具函数之 placeChild
placeChild
函数主要负责确定新 Fiber
节点在 Fiber
树中的位置,并根据情况标记该节点是需要移动、插入还是保持原位,同时返回更新后的 lastPlacedIndex
值。lastPlacedIndex
用于记录上一个已放置节点的索引,以此来判断当前节点是否需要移动。
函数参数含义
newFiber
:类型为Fiber
,代表新创建或复用的Fiber
节点,需要为其确定在Fiber
树中的位置。lastPlacedIndex
:类型为number
,表示上一个已放置节点的索引,用于判断当前节点是否需要移动。newIndex
:类型为number
,是新Fiber
节点在新子节点数组中的索引。
function placeChild(newFiber: Fiber,lastPlacedIndex: number,newIndex: number,
): number {// 设置新 Fiber 节点的索引newFiber.index = newIndex;// 处理不需要跟踪副作用的情况 (初始化时)if (!shouldTrackSideEffects) {newFiber.flags |= Forked;return lastPlacedIndex;}// 获取旧 Fiber 节点const current = newFiber.alternate;if (current !== null) {const oldIndex = current.index;if (oldIndex < lastPlacedIndex) {// 说明需要移动newFiber.flags |= Placement | PlacementDEV;return lastPlacedIndex;} else {// This item can stay in place.return oldIndex;}} else {// 处理不存在旧 Fiber 节点的情况// This is an insertion.newFiber.flags |= Placement | PlacementDEV;return lastPlacedIndex;}
}
工具函数之 createChild
createChild
函数用于根据不同类型的 newChild
创建对应的 Fiber 节点,并将其连接到父 Fiber(returnFiber
)。它是 React 协调过程中的核心函数之一,负责处理各种类型的 React 元素,包括文本节点、普通元素、Portal、延迟加载组件等。
function createChild(returnFiber: Fiber,newChild: any,lanes: Lanes,
): Fiber | null {// 处理原始类型(字符串、数字、大整数)if ((typeof newChild === 'string' && newChild !== '') ||typeof newChild === 'number' ||typeof newChild === 'bigint') {const created = createFiberFromText('' + newChild,returnFiber.mode,lanes,);created.return = returnFiber;return created;}// 处理对象类型if (typeof newChild === 'object' && newChild !== null) {switch (newChild.$$typeof) {case REACT_ELEMENT_TYPE: {const created = createFiberFromElement(newChild,returnFiber.mode,lanes,);coerceRef(created, newChild);created.return = returnFiber;return created;}case REACT_PORTAL_TYPE: {const created = createFiberFromPortal(newChild,returnFiber.mode,lanes,);created.return = returnFiber;return created;}case REACT_LAZY_TYPE: {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);let resolvedChild;const payload = newChild._payload;const init = newChild._init;resolvedChild = init(payload);const created = createChild(returnFiber, resolvedChild, lanes);// currentDebugInfo = prevDebugInfo;return created;}}if (isArray(newChild) ||getIteratorFn(newChild) ||(enableAsyncIterableChildren &&typeof newChild[ASYNC_ITERATOR] === 'function')) {const created = createFiberFromFragment(newChild,returnFiber.mode,lanes,null,);created.return = returnFiber;return created;}// Usable node types//// Unwrap the inner value and recursively call this function again.if (typeof newChild.then === 'function') {const thenable: Thenable<any> = (newChild: any);// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const created = createChild(returnFiber,unwrapThenable(thenable),lanes,);// currentDebugInfo = prevDebugInfo;return created;}if (newChild.$$typeof === REACT_CONTEXT_TYPE) {const context: ReactContext<mixed> = (newChild: any);return createChild(returnFiber,readContextDuringReconciliation(returnFiber, context, lanes),lanes,);}// throwOnInvalidObjectType(returnFiber, newChild);}return null;
}
工具函数之 updateSlot
updateSlot
函数主要用于根据新的子元素(newChild
)和旧 fiber(oldFiber
)来更新父 fiber(returnFiber
)的插槽内容。它会根据子元素的类型(文本、对象、数组、可迭代对象、Promise 等)进行不同的处理,以实现对纤维的更新或返回 null
(当键不匹配或无法处理子元素类型时)。
function updateSlot(returnFiber: Fiber,oldFiber: Fiber | null,newChild: any,lanes: Lanes,
): Fiber | null {// Update the fiber if the keys match, otherwise return null.const key = oldFiber !== null ? oldFiber.key : null;if ((typeof newChild === 'string' && newChild !== '') ||typeof newChild === 'number' ||typeof newChild === 'bigint') {if (key !== null) {return null;}return updateTextNode(returnFiber,oldFiber,'' + newChild,lanes,);}if (typeof newChild === 'object' && newChild !== null) {switch (newChild.$$typeof) {case REACT_ELEMENT_TYPE: {if (newChild.key === key) {const updated = updateElement(returnFiber,oldFiber,newChild,lanes,);return updated;} else {return null;}}case REACT_PORTAL_TYPE: {if (newChild.key === key) {return updatePortal(returnFiber, oldFiber, newChild, lanes);} else {return null;}}case REACT_LAZY_TYPE: {let resolvedChild;const payload = newChild._payload;const init = newChild._init;resolvedChild = init(payload);const updated = updateSlot(returnFiber,oldFiber,resolvedChild,lanes,);return updated;}}if (isArray(newChild) ||getIteratorFn(newChild) ||(enableAsyncIterableChildren &&typeof newChild[ASYNC_ITERATOR] === 'function')) {if (key !== null) {return null;}const updated = updateFragment(returnFiber,oldFiber,newChild,lanes,null,);return updated;}// Usable node types//// Unwrap the inner value and recursively call this function again.if (typeof newChild.then === 'function') {const thenable: Thenable<any> = (newChild: any);const updated = updateSlot(returnFiber,oldFiber,unwrapThenable(thenable),lanes,);return updated;}if (newChild.$$typeof === REACT_CONTEXT_TYPE) {const context: ReactContext<mixed> = (newChild: any);return updateSlot(returnFiber,oldFiber,readContextDuringReconciliation(returnFiber, context, lanes),lanes,);}throwOnInvalidObjectType(returnFiber, newChild);}return null;
}
工具函数之 mapRemainingChildren
创建一个新的 Map
对象 existingChildren
,用于存储子 Fiber
节点。Map
的键可以是字符串类型(当子 Fiber
节点有 key
属性时)或者数字类型(当子 Fiber
节点没有 key
属性时,使用其 index
作为键),值为对应的 Fiber
节点。
function mapRemainingChildren(currentFirstChild: Fiber,//当前父 Fiber 节点的第一个子 Fiber 节点。
): Map<string | number, Fiber> {const existingChildren: Map<string | number, Fiber> = new Map();let existingChild: null | Fiber = currentFirstChild;while (existingChild !== null) {if (existingChild.key !== null) {existingChildren.set(existingChild.key, existingChild);} else {// 用索引作为keyexistingChildren.set(existingChild.index, existingChild);}// 处理下一个兄弟节点existingChild = existingChild.sibling;}// 返回存储子 Fiber 节点的 Mapreturn existingChildren;
}
工具函数之 updateFromMap
updateFromMap
函数主要用于处理以 Map
结构存储的现有子节点(existingChildren
)与新子节点(newChild
)的匹配和更新逻辑。它会根据子节点的类型(文本、元素、Portal、延迟加载组件等),从 Map
中查找匹配的旧纤维(Fiber
),并执行更新、复用或创建操作。
function updateFromMap(existingChildren: Map<string | number, Fiber>,returnFiber: Fiber,newIdx: number,newChild: any,lanes: Lanes,
): Fiber | null {// 处理普通类型的if ((typeof newChild === 'string' && newChild !== '') ||typeof newChild === 'number' ||typeof newChild === 'bigint') {const matchedFiber = existingChildren.get(newIdx) || null;return updateTextNode(returnFiber,matchedFiber,'' + newChild,lanes,);}// 处理对象类型if (typeof newChild === 'object' && newChild !== null) {switch (newChild.$$typeof) {case REACT_ELEMENT_TYPE: {const matchedFiber =existingChildren.get(newChild.key === null ? newIdx : newChild.key,) || null;// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const updated = updateElement(returnFiber,matchedFiber,newChild,lanes,);// currentDebugInfo = prevDebugInfo;return updated;}case REACT_PORTAL_TYPE: {const matchedFiber =existingChildren.get(newChild.key === null ? newIdx : newChild.key,) || null;return updatePortal(returnFiber, matchedFiber, newChild, lanes);}case REACT_LAZY_TYPE: {// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);let resolvedChild;const payload = newChild._payload;const init = newChild._init;resolvedChild = init(payload);const updated = updateFromMap(existingChildren,returnFiber,newIdx,resolvedChild,lanes,);// currentDebugInfo = prevDebugInfo;return updated;}}if (isArray(newChild) ||getIteratorFn(newChild) ||(enableAsyncIterableChildren &&typeof newChild[ASYNC_ITERATOR] === 'function')) {const matchedFiber = existingChildren.get(newIdx) || null;// const prevDebugInfo = pushDebugInfo(newChild._debugInfo);const updated = updateFragment(returnFiber,matchedFiber,newChild,lanes,null,);// currentDebugInfo = prevDebugInfo;return updated;}// Usable node types//// Unwrap the inner value and recursively call this function again.if (typeof newChild.then === 'function') {const thenable: Thenable<any> = (newChild: any);// const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo);const updated = updateFromMap(existingChildren,returnFiber,newIdx,unwrapThenable(thenable),lanes,);// currentDebugInfo = prevDebugInfo;return updated;}if (newChild.$$typeof === REACT_CONTEXT_TYPE) {const context: ReactContext<mixed> = (newChild: any);return updateFromMap(existingChildren,returnFiber,newIdx,readContextDuringReconciliation(returnFiber, context, lanes),lanes,);}// throwOnInvalidObjectType(returnFiber, newChild);}return null;
}
工具函数之 updateElement
updateElement
函数用于处理 React 元素的更新或创建操作。它会根据元素类型、当前纤维状态以及新旧元素的匹配情况,决定是复用现有 fiber(useFiber
)还是创建新 fiber(createFiberFromElement
)。该函数是 React 协调过程的核心部分,负责处理普通元素和 Fragment 类型的更新逻辑。
function updateElement(returnFiber: Fiber,current: Fiber | null,element: ReactElement,lanes: Lanes,
): Fiber {const elementType = element.type;if (elementType === REACT_FRAGMENT_TYPE) {const updated = updateFragment(returnFiber,current,element.props.children,lanes,element.key,);// validateFragmentProps(element, updated, returnFiber);return updated;}if (current !== null) {if (current.elementType === elementType ||(typeof elementType === 'object' &&elementType !== null &&elementType.$$typeof === REACT_LAZY_TYPE &&resolveLazy(elementType) === current.type)) {// Move based on indexconst existing = useFiber(current, element.props);coerceRef(existing, element);existing.return = returnFiber;return existing;}}// Insertconst created = createFiberFromElement(element, returnFiber.mode, lanes);coerceRef(created, element);created.return = returnFiber;return created;
}
工具函数之 updateFragement
function updateFragment(returnFiber: Fiber,current: Fiber | null,fragment: Iterable<React$Node>,lanes: Lanes,key: null | string,
): Fiber {if (current === null || current.tag !== Fragment) {// Insertconst created = createFiberFromFragment(fragment,returnFiber.mode,lanes,key,);created.return = returnFiber;return created;} else {// Updateconst existing = useFiber(current, fragment);existing.return = returnFiber;return existing;}
}
工具函数之 updateTextNode
updateTextNode
函数用于处理文本节点的更新或创建操作。它会根据当前是否存在旧 fiber(current
)以及旧 fiber 的类型,决定是复用现有 fiber 还是创建新 fiber。
function updateTextNode(returnFiber: Fiber,current: Fiber | null,textContent: string,lanes: Lanes,
) {if (current === null || current.tag !== HostText) {// Insertconst created = createFiberFromText(textContent, returnFiber.mode, lanes);created.return = returnFiber;return created;} else {// Updateconst existing = useFiber(current, textContent);existing.return = returnFiber;return existing;}
}
全局变量
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;
fiber树结构
<div><p></p><div></div><span></span>
</div>
react支持浏览器的内置组件
React DOM 组件 – React 中文文档
1、通用组件(如 <div>)
这些组件在 React 中可以使用 React 特有的属性,如 ref 与 dangerouslySetInnerHTML。
2、表单组件
- <input>
- <select>
- <textarea>
将 value 作为 prop 传递给这些组件会将其变为 受控组件。
3、资源和mata组件
- <link>
- <meta>
- <script>
- <style>
- <title>
4、所有html组件
<aside>
<audio>
<b>
<base>
<bdi>
<bdo>
<blockquote>
<body>
<br>等等
5、所有svg组件
单例宿主组件(HostSingleton
)
一、定义
单例宿主组件指的是在整个 React 应用中只能存在一个实例的宿主组件。宿主组件一般代表原生 DOM 元素,像 <div>
、<span>
这类。单例宿主组件可能和特定的 DOM 元素或者系统资源有关,这些资源在应用里仅允许有一个实例。
二、用途
- 资源管理:在某些场景下,应用仅需一个特定的 DOM 元素或者系统资源,例如全局的模态框、全局的通知栏等。使用单例宿主组件可以保证这些资源仅被创建一次,避免资源的重复创建与浪费。
- 状态管理:单例宿主组件可以用来管理全局状态。因为只有一个实例,所以状态的管理会更加简单和可控。
可提升的宿主组件(HostHoistable)
一、定义
可提升的宿主组件是指那些可以被提升到更高级别 DOM 树中的宿主组件。提升操作通常是为了优化渲染性能,减少不必要的 DOM 操作。
二、用途
- 性能优化:当某些宿主组件的渲染频率较高或者对性能影响较大时,将它们提升到更高级别的 DOM 树中,可以减少这些组件的重新渲染次数,从而提升应用的性能。
- 减少 DOM 操作:通过提升组件,可以避免在每次渲染时都创建和销毁这些组件对应的 DOM 元素,减少 DOM 操作的开销。