React 源码专栏之 createRoot() 执行流程

我们在使用 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);
}

首先第一步它会对一些选项属性进行处理:

  1. isStrictMode:如果选项中启用了严格模式,则设置为 true。

  2. concurrentUpdatesByDefaultOverride:如果选项中启用了并发更新,则设置为 true。

    虽然 React 18 引入了并发模式,但 unstable_concurrentUpdatesByDefault 选项允许开发者在更细粒度的基础上控制并发更新的行为。这个选项可以在调用 createRoot 时通过配置来显式启用。

    jsx 复制代码
    const root = ReactDOM.createRoot(document.getElementById("root"), {
      unstable_concurrentUpdatesByDefault: true,
    });
    
    root.render(<App />);

    在 createRoot 函数中,如果传入了 unstable_concurrentUpdatesByDefault 选项且全局配置 allowConcurrentByDefault 为 true,那么 concurrentUpdatesByDefaultOverride 会被设置为 true。

    这个时候是可以走进 if 分支里面的,如果我们在项目中没有加这个是直接走到 const root = createContainer(...) 这段代码里面的。

  3. identifierPrefix:设置标识符前缀,用于调试和日志记录。

  4. onRecoverableError:设置可恢复的错误处理函数。

  5. 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());
    }
  }
}

从上面的结构中,我们需要知道的一些关键信息:

  1. tag:标记根节点的类型。它为不同的元素类型定义了不同的 tag。

    ts 复制代码
    export 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;
  2. containerInfo:存储根 DOM 节点的信息。

    它就是我们整个 React 项目的入口文件,也就是我们项目中那个 index.html 文件里面的节点。

  3. pendingChildren:待处理的子节点。

  4. current:指向当前的 Fiber 树根节点。

  5. pingCache、finishedWork、timeoutHandle、context、pendingContext 等:用于管理 Fiber 树的各种状态和缓存。

  6. callbackNode 和 callbackPriority:用于调度更新。

  7. 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 应用的状态和更新过程。它包含以下几个关键属性:

  1. current:指向当前的 Fiber 树的根节点,即 HostRootFiber。

  2. containerInfo:存储与根容器(例如 DOM 容器)相关的信息。

  3. pendingChildren:暂存当前正在渲染的子元素。

  4. finishedWork:指向已完成的工作树,等待提交。

  5. callbackNode 和 callbackPriority:与调度优先级相关的属性。

HostRootFiber 是 Fiber 树的根节点,它代表了应用的根组件。HostRootFiber 是 Fiber 树中最高层级的节点,其类型为 HostRoot。主要属性包括:

  1. tag:标记节点类型(对于 HostRootFiber 来说是 HostRoot)。

  2. stateNode:指向与此 Fiber 节点关联的本地状态(对于 HostRootFiber 来说是 FiberRootNode 对象本身)。

  3. 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 在渲染和更新过程中使用的,以确保组件的状态在不同的渲染周期中保持一致。

  1. memoizedState 存储了与当前 Fiber 节点相关的状态信息。这些状态信息可以包括组件的局部状态、hook 状态(如 useState、useReducer 等)。

  2. memoizedState 使得 React 能够在更新过程中高效地比较新旧状态。如果状态没有变化,React 可以跳过不必要的重新渲染。

  3. 在某些情况下(例如在调度过程中被中断),React 可以使用 memoizedState 恢复到先前的状态,以继续未完成的工作。

在函数组件中,memoizedState 通常用于存储 Hook 的状态。例如,当你使用 useState 时,React 会将状态存储在 memoizedState 中。

jsx 复制代码
function Moment() {
  const [count, setCount] = useState(0);
}

在这个例子中,count 的值会存储在对应 Fiber 节点的 memoizedState 中。而类组件的 this.state 也是这样。更多详细的内容会在后面更加详细地讲解。

firstBaseUpdate 和 lastBaseUpdate 是循环链表的原因是为了有效地管理和处理组件的更新。在 React 中,它有助于实现高效的更新管理:

  1. 插入新更新:可以在常数时间内将新更新添加到队列中。

  2. 遍历和处理:可以方便地遍历所有更新,处理它们并应用必要的状态变化。

  3. 循环结构:确保在任何情况下都能回到队列的起点,简化了边界条件处理。

通过使用循环链表,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 函数的主要作用是:

  1. 创建或复用备用 Fiber 节点:如果当前 Fiber 节点没有备用节点(首次渲染的时候是没有的),创建一个新的备用节点;如果有备用节点,复用它。

  2. 复制属性和状态:将当前 Fiber 节点的属性、状态和依赖项复制到工作中的 Fiber 节点。

  3. 重置效果标记:重置工作中的 Fiber 节点的效果标记,以准备新的渲染和更新。

  4. 处理开发环境的调试和热重载:在开发环境中,处理调试信息和热重载相关的逻辑。

通过这些步骤,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 能够高效地进行深度优先遍历、状态更新和渲染。这在我们后面中要讲到的可中断渲染有着非常重要的支撑。

这是一个小图,说明了创建的对象及其一些属性:

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
小牛itbull3 小时前
ReactPress:构建高效、灵活、可扩展的开源发布平台
react.js·开源·reactpress
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、5 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la7 小时前
vue的样式知识点
前端·javascript·vue.js