在之前文章中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,更新为true
shouldTrackSideEffects: boolean,
): ChildReconciler {
//。。。
// function reconcileChildFibers(){}
return reconcileChildFibers;
}
reconcileChildFibers
reconcileChildFibers 函数主要用于对比新旧子节点,生成新的 Fiber 节点树,以此实现 React 组件树的更新。主要实现是调用内部的 reconcileChildFibersImpl 函数完成调和操作,最终返回 新的第一个子 Fiber 节点。
函数参数含义:
- returnFiber,父 Fiber 节点。
- currentFirstChild,当前的子 Fiber 节点。
- newChild,新的子节点。
- lanes,渲染优先级。
function reconcileChildFibers(
returnFiber: Fiber,// 父节点fiber
currentFirstChild: 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设置为newChild
if (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);
// 处理ref
coerceRef(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 {
// 初始owner
let 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$ElementType
key: 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 roots
mode |= 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 through
default: {
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;
}
}
}
// 创建fiber
const 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常量 为7
const 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 updates
implementation: 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;
// 旧fiber
let 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 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 list
dependencies.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,// 父节点fiber
currentFirstChild: 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,// 待处理的props
current.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 reconciliation
workInProgress.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赋给当前fiber
workInProgress.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 {
// 用索引作为key
existingChildren.set(existingChild.index, existingChild);
}
// 处理下一个兄弟节点
existingChild = existingChild.sibling;
}
// 返回存储子 Fiber 节点的 Map
return 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 index
const existing = useFiber(current, element.props);
coerceRef(existing, element);
existing.return = returnFiber;
return existing;
}
}
// Insert
const 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) {
// Insert
const created = createFiberFromFragment(
fragment,
returnFiber.mode,
lanes,
key,
);
created.return = returnFiber;
return created;
} else {
// Update
const 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) {
// Insert
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
} else {
// Update
const 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支持浏览器的内置组件
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 操作的开销。