我们在使用 create-react-app 创建项目的时候都可以看到,在 index.jsx 目录下有这样的代码:
jsx
const root = ReactDOM.createRoot(document.getElementById("root"));
该段代码作为整个项目中的入口,它通过 document.getElementById
方法来获取 HTML 页面中的一个元素。这个元素的 ID 是 root。并使用 ReactDOM.createRoot
方法创建一个 React 应用的根节点。
在前面的内容中,我们讲到项目中编写的代码最终都会被 jsx 函数进行处理,但是在开发环境,会被 jsxDev 函数执行,如下所示:
并且会在该函数最后调用 ReactElement 函数并最终返回:
之后经过一系列验证之后,这些验证包括 props 等等,最终会返回一个 root 变量,也就是我们在代码上能看到的这个:
createRoot
ReactDOM.createRoot
函数负责创建和挂载 FiberRoot,它的函数定义如下所示:
ts
export function createRoot(
container: Element | Document | DocumentFragment,// div#root 根节点
options?: CreateRootOptions,
): RootType {
if (!isValidContainer(container)) {
throw new Error('createRoot(...): Target container is not a DOM element.');
}
warnIfReactDOMContainerInDEV(container);
let isStrictMode = false; // 严格模式
let concurrentUpdatesByDefaultOverride = false; // 设置更新模式
let identifierPrefix = ''; // 前缀
let onRecoverableError = defaultOnRecoverableError; // 可恢复的错误处理方法
let transitionCallbacks = null; // 过度回调
if (options !== null && options !== undefined) {
if (options.unstable_strictMode === true) {
isStrictMode = true;
}
if (
allowConcurrentByDefault &&
options.unstable_concurrentUpdatesByDefault === true
) {
concurrentUpdatesByDefaultOverride = true;
}
if (options.identifierPrefix !== undefined) {
identifierPrefix = options.identifierPrefix;
}
if (options.onRecoverableError !== undefined) {
onRecoverableError = options.onRecoverableError;
}
if (options.transitionCallbacks !== undefined) {
transitionCallbacks = options.transitionCallbacks;
}
}
// 拿到fiberRootNode
const root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
// 将 fiberNode 挂载到 container 对象上,也就是根目录
markContainerAsRoot(root.current, container);
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container;
// 第三步:在 div#root 上绑定各种事件,包括捕获和冒泡阶段
listenToAllSupportedEvents(rootContainerElement);
return new ReactDOMRoot(root);
}
首先第一步它会对一些选项属性进行处理:
-
isStrictMode:如果选项中启用了严格模式,则设置为 true。
-
concurrentUpdatesByDefaultOverride:如果选项中启用了并发更新,则设置为 true。
虽然 React 18 引入了并发模式,但 unstable_concurrentUpdatesByDefault 选项允许开发者在更细粒度的基础上控制并发更新的行为。这个选项可以在调用 createRoot 时通过配置来显式启用。
jsxconst root = ReactDOM.createRoot(document.getElementById("root"), { unstable_concurrentUpdatesByDefault: true, }); root.render(<App />);
在 createRoot 函数中,如果传入了
unstable_concurrentUpdatesByDefault
选项且全局配置allowConcurrentByDefault
为 true,那么concurrentUpdatesByDefaultOverride
会被设置为 true。这个时候是可以走进 if 分支里面的,如果我们在项目中没有加这个是直接走到
const root = createContainer(...)
这段代码里面的。 -
identifierPrefix:设置标识符前缀,用于调试和日志记录。
-
onRecoverableError:设置可恢复的错误处理函数。
-
transitionCallbacks:设置过渡回调函数。
这些逻辑都走完之后,它会调用 createContainer(...)
函数,该函数则会创建一个 FiberRootNode。如下代码为 createContainer 函数的定义:
createContainer
ts
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: (error: mixed) => void,
transitionCallbacks: null | TransitionTracingCallbacks
): OpaqueRoot {
const hydrate = false;
const initialChildren = null;
return createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks
);
}
在上面的参数中,tag 被设置为 1,也就是传入 ConcurrentRoot 参数,表示标志根节点的类型,它会在函数调用阶段通过传入来实现:
ts
export const LegacyRoot = 0;
export const ConcurrentRoot = 1;
在这种模式下,React 可以异步地处理更新和渲染任务。
const hydrate = false;
这个常量 hydrate 被设置为 false,表示当前不进行服务端渲染(SSR)的水合操作。水合操作是指将服务器渲染的 HTML 内容转化为可交互的 React 组件。
const initialChildren = null;
这个常量 initialChildren 被设置为 null,表示初始没有子节点。通常在创建根节点时不会有初始子节点,子节点会在之后的渲染过程中添加。
最后调用 createFiberRoot,也就是说 createContainer 函数的主要工作是调用 createFiberRoot 函数,并将所有参数传递给它。createFiberRoot 函数负责实际创建 FiberRoot 实例,并初始化根 Fiber 节点和相关配置。
createFiberRoot
这个函数 createFiberRoot 是 React 内部用于创建和初始化 FiberRoot 的函数。FiberRoot 是 React 应用的根节点数据结构,它包含了应用的根组件、状态和更新队列等信息。
ts
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
initialChildren: ReactNodeList,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: null | ((error: mixed) => void),
transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
/** 创建 FiberRoot */
const root: FiberRoot = (new FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
onRecoverableError,
): any);
/** 设置服务端渲染回调 */
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
/** 设置过渡回调 */
if (enableTransitionTracing) {
root.transitionCallbacks = transitionCallbacks;
}
/** 创建 HostRootFiber */
const uninitializedFiber = createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
/** 将 HostRootFiber 挂载到 FiberRoot 的 current 属性上 */
root.current = uninitializedFiber;
/** 将 HostRootFiber 的 stateNode 设置为 FiberRoot */
uninitializedFiber.stateNode = root;
/** 设置 HostRootFiber 的 memoizedState */
if (enableCache) {
const initialCache = createCache();
retainCache(initialCache);
root.pooledCache = initialCache;
retainCache(initialCache);
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: initialCache,
transitions: null,
pendingSuspenseBoundaries: null,
};
uninitializedFiber.memoizedState = initialState;
} else {
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
transitions: null,
pendingSuspenseBoundaries: null,
};
uninitializedFiber.memoizedState = initialState;
}
// 初始化 updateQueue,对于 RootFiber,queue.share.pending 上面存储着element
initializeUpdateQueue(uninitializedFiber);
return root;
}
FiberRootNode
在源码中,FiberRoot 主要有以下这些定义:
ts
function FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
onRecoverableError
) {
this.tag = tag;
this.containerInfo = containerInfo; // div#root
this.pendingChildren = null;
this.current = null; // fiber树
this.pingCache = null;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.callbackNode = null;
this.callbackPriority = NoLane;
this.eventTimes = createLaneMap(NoLanes);
this.expirationTimes = createLaneMap(NoTimestamp);
this.pendingLanes = NoLanes;
this.suspendedLanes = NoLanes;
this.pingedLanes = NoLanes;
this.expiredLanes = NoLanes;
this.mutableReadLanes = NoLanes;
this.finishedLanes = NoLanes;
this.entangledLanes = NoLanes;
this.entanglements = createLaneMap(NoLanes);
this.identifierPrefix = identifierPrefix;
this.onRecoverableError = onRecoverableError;
if (enableCache) {
this.pooledCache = null;
this.pooledCacheLanes = NoLanes;
}
if (supportsHydration) {
this.mutableSourceEagerHydrationData = null;
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}
if (enableTransitionTracing) {
this.transitionCallbacks = null;
const transitionLanesMap = (this.transitionLanes = []);
for (let i = 0; i < TotalLanes; i++) {
transitionLanesMap.push(null);
}
}
if (enableProfilerTimer && enableProfilerCommitHooks) {
this.effectDuration = 0;
this.passiveEffectDuration = 0;
}
if (enableUpdaterTracking) {
this.memoizedUpdaters = new Set();
const pendingUpdatersLaneMap = (this.pendingUpdatersLaneMap = []);
for (let i = 0; i < TotalLanes; i++) {
pendingUpdatersLaneMap.push(new Set());
}
}
}
从上面的结构中,我们需要知道的一些关键信息:
-
tag:标记根节点的类型。它为不同的元素类型定义了不同的 tag。
tsexport const FunctionComponent = 0; // 函数组件 export const ClassComponent = 1; // 类组件 export const IndeterminateComponent = 2; // 初始的未确定组件。 export const HostRoot = 3; // 根组件 React 应用的入口点。 export const HostPortal = 4; // Portal 子树,它可以作为进入不同渲染器的入口点。 export const HostComponent = 5; // 原生组件,例如 <div> 或 <span> export const HostText = 6; // 文本节点 export const Fragment = 7; // fragment export const Mode = 8; // 用于启用某些 React 功能,如严格模式 (<StrictMode>). export const ContextConsumer = 9; // context消费者 export const ContextProvider = 10; // context 提供者 export const ForwardRef = 11; // forwardRef 创建的组件 export const Profiler = 12; // Profiler 组件 (<Profiler>),用于收集渲染性能数据 export const SuspenseComponent = 13; // Suspense 组件 export const MemoComponent = 14; // React.memo 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;
-
containerInfo:存储根 DOM 节点的信息。
它就是我们整个 React 项目的入口文件,也就是我们项目中那个
index.html
文件里面的节点。 -
pendingChildren:待处理的子节点。
-
current:指向当前的 Fiber 树根节点。
-
pingCache、finishedWork、timeoutHandle、context、pendingContext 等:用于管理 Fiber 树的各种状态和缓存。
-
callbackNode 和 callbackPriority:用于调度更新。
-
eventTimes 和 expirationTimes:用于管理事件和更新的时间。
FiberRoot 是一个特殊的节点,作为 React 的根节点,它包含了整个应用的必要元信息。其 current
属性指向实际的 Fiber 树,每次构建新的 Fiber 树时,它会将 current
重新指向新的 HostRoot。
HostRootFiber
HostRootFiber 对应着 Fiber 架构中未被初始化过的 Fiber 树,它被挂载到 FiberRoot 的 current 属性上,它的 stateNode 属性也被设置为 FiberRoot 。
ts
export function createHostRootFiber(
tag: RootTag,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
mode = ConcurrentMode;
if (isStrictMode === true) {
mode |= StrictLegacyMode;
if (enableStrictEffects) {
mode |= StrictEffectsMode;
}
} else if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode |= StrictLegacyMode | StrictEffectsMode;
}
if (
!enableSyncDefaultUpdates ||
// Only for internal experiments.
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
) {
mode |= ConcurrentUpdatesByDefaultMode;
}
} else {
mode = NoMode;
}
if (enableProfilerTimer && isDevToolsPresent) {
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
在这个函数里面主要做的事情是设置 React Fiber 架构的工作模式 (Concurrent 模式、严格模式、ConcurrentUpdatesByDefaultMode 模式)。并调用 createFiber 函数创建一个 fiber 返回。
而 createFiber 函数最终会调用 FiberNode 这个构造函数:
ts
const createFiber = function (
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode
): Fiber {
return new FiberNode(tag, pendingProps, key, mode);
};
这里就是我们之前在 jsx 章节里面讲到的内容:
FiberRootNode 是一个更高级别的对象,负责管理整个 React 应用的状态和更新过程。它包含以下几个关键属性:
-
current:指向当前的 Fiber 树的根节点,即 HostRootFiber。
-
containerInfo:存储与根容器(例如 DOM 容器)相关的信息。
-
pendingChildren:暂存当前正在渲染的子元素。
-
finishedWork:指向已完成的工作树,等待提交。
-
callbackNode 和 callbackPriority:与调度优先级相关的属性。
HostRootFiber 是 Fiber 树的根节点,它代表了应用的根组件。HostRootFiber 是 Fiber 树中最高层级的节点,其类型为 HostRoot。主要属性包括:
-
tag:标记节点类型(对于 HostRootFiber 来说是 HostRoot)。
-
stateNode:指向与此 Fiber 节点关联的本地状态(对于 HostRootFiber 来说是 FiberRootNode 对象本身)。
-
updateQueue:存储该 Fiber 节点上的更新队列。
具体来说,FiberRootNode 的 current 属性指向这个 HostRootFiber 节点。HostRootFiber 节点的 stateNode 属性指向 FiberRootNode 对象本身。
在初次渲染时,React 会通过 FiberRootNode 开始构建 Fiber 树,从 HostRootFiber 节点开始向下递归创建子节点,直到构建出整个应用的 Fiber 树。
每次更新时,React 会基于现有的 Fiber 树和新的更新,创建一个新的 Fiber 树,然后将 FiberRootNode 的 current 属性更新为新的 HostRootFiber 节点。
当应用状态发生变化时,React 会将更新添加到 HostRootFiber 节点的 updateQueue 中。 React 的调度器会处理这些更新,并基于现有的 Fiber 树创建一个新的 Fiber 树。
新的 Fiber 树构建完成后,FiberRootNode 的 current 属性会被更新为新的 HostRootFiber 节点,指向新的 Fiber 树。
之所以会有这样的表现,主要是在 createFiberRoot 函数中有如下定义:
js
// 将 HostRootFiber 挂载到 FiberRoot 的 current 属性上
root.current = uninitializedFiber;
// 将 HostRootFiber 的 stateNode 设置为 FiberRoot
uninitializedFiber.stateNode = root;
它们的关系图请看下图所示:
除了这些之外,FiberNode 中还有一些其他属性是需要我们现在知道的,如下图所示:
child 指向的是子元素,而 return 指向的父元素,两者相互指向,而 FiberNode 为当前的 Fiber 树的根节点,它没有父节点,它的 return 值为空也就讲得通了。
React 对 Fiber 采用这种双向链表的数据结构,在进行深度优先遍历时,React 可以利用 child 引用快速找到子节点。当需要返回父节点时,利用 return 引用,可以快速找到父节点,避免了重新计算父节点的路径。
当某个节点的状态或属性发生变化时,React 可以通过 return 引用快速找到父节点,并继续向上查找,直到找到需要重新渲染的祖先节点。通过 child 引用,React 可以快速遍历并更新所有需要重新渲染的子节点。
在初次渲染时,React 可以通过 child 引用逐层创建并渲染 Fiber 树。在更新过程中,React 可以通过 return 引用找到需要更新的节点,并通过 child 引用进行部分更新,而不是重新渲染整个树。
在 createFiberRoot 函数的最后调用了 initializeUpdateQueue 用于初始化 rootFiber.updateQueue:
js
initializeUpdateQueue(uninitializedFiber);
要想了解该函数的作用,那么我们先来了解一下该函数是怎么定义的:
ts
// react\packages\react-reconciler\src\ReactFiberClassUpdateQueue.old.js
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
interleaved: null,
lanes: NoLanes,
},
effects: null,
};
fiber.updateQueue = queue;
}
这段函数的主要作用是初始化一个 UpdateQueue 对象,并将其附加到给定的 Fiber 对象上。具体来说,它的作用是为 Fiber 节点设置一个更新队列,以便管理该节点的状态更新。
baseState:初始化为 fiber.memoizedState,表示 Fiber 节点当前的状态。firstBaseUpdate 和 lastBaseUpdate:初始化为 null,表示更新队列的起始和结束。更新队列是一个链表,这两个属性用于指向链表的头和尾。
memoizedState、firstBaseUpdate 和 lastBaseUpdate 需要你特别了解一下
在 React 的 Fiber 架构中,fiber.memoizedState 是一个非常重要的属性,它用于存储当前 Fiber 节点的状态。这些状态数据是 React 在渲染和更新过程中使用的,以确保组件的状态在不同的渲染周期中保持一致。
-
memoizedState 存储了与当前 Fiber 节点相关的状态信息。这些状态信息可以包括组件的局部状态、hook 状态(如 useState、useReducer 等)。
-
memoizedState 使得 React 能够在更新过程中高效地比较新旧状态。如果状态没有变化,React 可以跳过不必要的重新渲染。
-
在某些情况下(例如在调度过程中被中断),React 可以使用 memoizedState 恢复到先前的状态,以继续未完成的工作。
在函数组件中,memoizedState 通常用于存储 Hook 的状态。例如,当你使用 useState 时,React 会将状态存储在 memoizedState 中。
jsx
function Moment() {
const [count, setCount] = useState(0);
}
在这个例子中,count 的值会存储在对应 Fiber 节点的 memoizedState 中。而类组件的 this.state 也是这样。更多详细的内容会在后面更加详细地讲解。
firstBaseUpdate 和 lastBaseUpdate 是循环链表的原因是为了有效地管理和处理组件的更新。在 React 中,它有助于实现高效的更新管理:
-
插入新更新:可以在常数时间内将新更新添加到队列中。
-
遍历和处理:可以方便地遍历所有更新,处理它们并应用必要的状态变化。
-
循环结构:确保在任何情况下都能回到队列的起点,简化了边界条件处理。
通过使用循环链表,React 可以实现高效的更新管理,确保更新按照正确的顺序处理,并且简化了边界条件的处理逻辑。这些优势使得 React 的更新机制更加高效和可靠。
FiberRoot 和 rootFiber 的区别
可能很多人会多着两者的区别存在一些误区或者困惑,FiberRoot 和 RootFiber 是两个重要的概念,它们各自承担了不同的角色。
在我们开发的项目中,你可以理解为每一个组件都有一个由 rootFiber 构成的 fiber 树,但整个应用的根节点只有一个,那么就是 fiberRoot,fiberRoot 可以指向不同的 rootFiber 以渲染不同的页面。
FiberRoot 是管理整个应用状态和更新的对象。它包含当前 Fiber 树的根节点引用(即 rootFiber)。
rootFiber 是 Fiber 树的根节点,代表应用的根组件。通过 stateNode 属性指向 FiberRoot 对象,从而形成一个双向引用。
一个 React 应用通常有一个 FiberRoot 对象,但可以有多个 rootFiber 节点。每个 rootFiber 节点代表一个独立的渲染树。
如果一个应用使用 ReactDOM.render 渲染多个独立的根组件,每个根组件会有自己的 rootFiber 节点。每个 rootFiber 节点与一个 FiberRoot 对象关联,管理其对应渲染树的状态和更新。
js
// 第一个根组件
ReactDOM.render(<App1 />, document.getElementById("root1"));
// 第二个根组件
ReactDOM.render(<App2 />, document.getElementById("root2"));
在这种情况下,会有两个独立的 FiberRoot 对象,每个对象有一个 rootFiber 节点,分别管理 App1 和 App2 的渲染树。
假设我们有这样的代码:
jsx
// rootFiber 对应的组件
<RootComponent>
<ChildComponent1 />
<ChildComponent2>
<SubChildComponent />
</ChildComponent2>
</RootComponent>
在这段代码中 RootComponent 是 rootFiber 对应的根组件。ChildComponent1、ChildComponent2 和 SubChildComponent 是普通的 Fiber 节点,不是 rootFiber。
代码又回到 createRoot 函数这里,在这里有调用了 markContainerAsRoot 函数:
js
// 将 fiberNode 挂载到 container 对象上,也就是跟目录
markContainerAsRoot(root.current, container);
我们继续来了解一下该函数的内部构成:
js
export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
node["__reactContainer$" + randomKey] = hostRoot;
}
所以最终结果如下图所示:
通过上面的图片我们可以得知,fiberRoot 它是整个应用的根节点, 绑定在 container._reactRootContainer
, 也就是绑定在真实 DOM 节点的 _reactRootContainer
属性上。
双缓存 Fiber 树
在 React 中同时会存在两颗 fiber 树,当前屏幕上显示内容对应的 fiber 树称为 current fiber 树,正在构建的 fiber 树称为 workInProgress Fiber 树,通过控制台可以看到如下输出:
React 应用的根节点通过使 current 指针在不同 Fiber 树的 rootFiber 间切换来完成 current Fiber 树指向的切换。 即当 workInProgress Fiber 树 构建完成交给 Renderer 渲染在页面上后,应用根节点的 current 指针指向
workInProgress Fiber 树,此时 workInProgress Fiber 树就变为 current Fiber 树。 每次状态更新都会产生新的 workInProgress Fiber 树,通过 current 与 workInProgress 的替换,完成 DOM 更新。
具体实现该功能的主要还是依赖于 createWorkInProgress 函数,代码如下所示:
ts
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 创建一个新的备用 Fiber 节点
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 建立双向引用
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// 复用现有的备用 Fiber 节点
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
// 重置 effect 标记
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
if (enableProfilerTimer) {
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
}
// 重置所有效果标记,除了静态效果标记
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,
};
// 这些属性将在父组件的协调过程中被覆盖
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
if (enableProfilerTimer) {
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
return workInProgress;
}
createWorkInProgress 函数的主要作用是:
-
创建或复用备用 Fiber 节点:如果当前 Fiber 节点没有备用节点(首次渲染的时候是没有的),创建一个新的备用节点;如果有备用节点,复用它。
-
复制属性和状态:将当前 Fiber 节点的属性、状态和依赖项复制到工作中的 Fiber 节点。
-
重置效果标记:重置工作中的 Fiber 节点的效果标记,以准备新的渲染和更新。
-
处理开发环境的调试和热重载:在开发环境中,处理调试信息和热重载相关的逻辑。
通过这些步骤,createWorkInProgress 函数确保在渲染和更新过程中,React 能够高效地管理和协调 Fiber 树的节点。
而最终的 fiber 树如下图所示:
总结
通过本文我们应该对 Fiber 有一个简单的框架了,并且了解了 React 中 FiberRoot 和 rootFiber 的概念及其实现原理
ReactDOM.createRoot
函数在 React 应用中负责创建和挂载 FiberRoot。这个函数接收一个 DOM 元素作为参数,代表应用的根节点,并通过 createContainer 和 createFiberRoot 函数创建一个 FiberRootNode。
createContainer
函数的主要任务是调用 createFiberRoot 函数,并将所有必要的参数传递给它,以创建一个 FiberRoot 实例。这包括初始化 Fiber 树根节点和相关配置。
createFiberRoot
函数创建和初始化 FiberRoot,并为其分配一个 HostRootFiber。
FiberRootNode
是管理整个 React 应用状态和更新的对象。它包含了指向当前 Fiber 树根节点的引用(即 HostRootFiber),以及其他用于管理更新、调度和渲染的属性。
HostRootFiber
是 Fiber 树的根节点,代表应用的根组件。它通过 stateNode 属性指向 FiberRootNode,形成双向引用。
React 使用双缓存技术来管理 Fiber 树,分为 current 树和 work-in-progress 树。current 树表示当前屏幕上显示的内容,而 work-in-progress 树是正在构建的新树。当 work-in-progress 树构建完成并交给 Renderer 渲染时,current 树会切换到新的 work-in-progress 树。
initializeUpdateQueue
函数用于初始化 Fiber 节点的更新队列。它创建一个 UpdateQueue 对象,并将其附加到给定的 Fiber 节点上,以便管理该节点的状态更新。
Fiber 树的节点通过 child 指向子节点,通过 return 指向父节点。这种双向链表结构使得 React 能够高效地进行深度优先遍历、状态更新和渲染。这在我们后面中要讲到的可中断渲染有着非常重要的支撑。
这是一个小图,说明了创建的对象及其一些属性: