React19源码系列之createRoot的执行流程是怎么的?

2024年12月5日,react发布了react19版本。后面一段时间都将学习它的源码,并着手记录。

react官网:react19新特性
https://react.dev/blog/2024/12/05/react-19

在用vite创建react项目的使用,main.tsx主文件都会有以下代码。

ts 复制代码
//import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

const root = createRoot(document.getElementById('root')!);
console.log('root',root)
root.render(
  // <StrictMode>
    <App />
  // </StrictMode>,
)

该代码是整个项目的入口。

接下来我们就看看 createRoot 是如何执行。

createRoot 函数

createRoot 函数的主要作用是创建一个 React 应用的根节点,并对其进行一系列初始化操作,最终返回一个 ReactDOMRoot 对象。

ts 复制代码
function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {

  // 用于控制是否默认启用并发更新
  const concurrentUpdatesByDefaultOverride = false;

  // 表示是否启用严格模式等。
  let isStrictMode = false;

  //
  let identifierPrefix = '';
  
  let onUncaughtError = defaultOnUncaughtError;
  let onCaughtError = defaultOnCaughtError;
  let onRecoverableError = defaultOnRecoverableError;
  let transitionCallbacks = null;

  //省略 options处理代码
  
// 创建容器对象 FiberRootNode
// FiberRootNode 对象,它是 React 协调器的核心数据结构,代表了整个 React 应用的根节点。
  const root = createContainer(
    container,
    ConcurrentRoot,//ConcurrentRoot 表示使用并发模式进行渲染。
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    transitionCallbacks,
  );
  
  //在dom container节点添加属性标记
  // 调用 markContainerAsRoot 函数,在 DOM 容器节点上添加一些属性标记,用于标识该节点是 React 应用的根节点。
  markContainerAsRoot(root.current, container);

  // 处理容器元素
  // 如果传入的 container 是一个注释节点,则将其父节点作为根容器元素;否则,直接使用 container 作为根容器元素。
  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE //8 注释节点
      ? (container.parentNode: any)
      : container;
  
  // 事件委托处理
  // 在div#root上绑定各种事件,包括捕获和冒泡阶段
  // 可以实现事件委托,提高事件处理的效率。
  listenToAllSupportedEvents(rootContainerElement);

// 根据创建的 FiberRootNode 对象 root,创建并返回一个 ReactDOMRoot 对象。这个对象提供了 render 方法,用于将 React 组件渲染到根节点上。
  return new ReactDOMRoot(root);
}

createContainer 函数

调用 createFiberRoot 函数来创建一个 FiberRoot 对象并返回。

函数参数含义:

containerInfo:类型为 Container,通常是一个 DOM 容器元素(如 div),用于指定 React 应用要挂载到的实际 DOM 节点。

tag:类型为 RootTag,用于标记根节点的类型,比如 ConcurrentRoot 表示使用并发模式渲染。

hydrationCallbacks:类型为 null | SuspenseHydrationCallbacks,在服务端渲染时,用于处理水合(hydration)过程中的回调函数。

isStrictMode:布尔类型,用于指示是否开启严格模式。严格模式会对组件进行额外的检查,帮助开发者发现潜在问题。

concurrentUpdatesByDefaultOverride:这个参数目前已被忽略,未来可能会移除。

identifierPrefix:字符串类型,用于为 React 元素生成唯一标识符的前缀。

onUncaughtError:错误处理回调函数,当发生未捕获的错误时会调用此函数。

onCaughtError:错误处理回调函数,当错误被捕获时调用,会提供更多错误信息,包括错误边界组件。

onRecoverableError:错误处理回调函数,当发生可恢复的错误时调用。

transitionCallbacks:类型为 null | TransitionTracingCallbacks,用于跟踪过渡(transition)状态的回调函数。

ts 复制代码
function createContainer(
  containerInfo: Container,// 实际的dom容器
  tag: RootTag,// 渲染类型 并发模式或者传统模式
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onUncaughtError: (
    error: mixed,
    errorInfo: {+componentStack?: ?string},
  ) => void,
  onCaughtError: (
    error: mixed,
    errorInfo: {
      +componentStack?: ?string,
      +errorBoundary?: ?React$Component<any, any>,
    },
  ) => void,
  onRecoverableError: (
    error: mixed,
    errorInfo: {+componentStack?: ?string},
  ) => void,
  transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
  
  // 设置为 false 表示不进行水合操作。水合操作通常用于服务端渲染,将服务端渲染的 HTML 与客户端的 React 应用进行连接。
  const hydrate = false;
  
  // 初始子元素,这里设置为 null,表示一开始没有初始的子元素。
  const initialChildren = null;
  
  //创建 FiberRoot
  // createFiberRoot 函数创建 FiberRoot 对象,该函数会根据传入的参数初始化 FiberRoot 的各种属性,包括根节点的类型、挂载的 DOM 容器、错误处理回调等,最终返回一个代表整个 React 应用根节点的 FiberRoot 对象。
  return createFiberRoot(
    containerInfo,
    tag,// 模式 并发模式
    hydrate,
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    transitionCallbacks,
    null,
  );
}

createFiberRoot 函数

createFiberRoot 函数是 React 中用于创建 FiberRoot 对象的核心函数,FiberRoot 是整个 React 应用的根节点,负责管理应用的渲染和更新流程。该函数会初始化根节点的各种属性,创建对应的 Fiber 节点,并进行一些状态和更新队列的初始化操作。

函数参数含义:

containerInfo:通常是一个 DOM 容器元素,用于指定 React 应用要挂载到的实际 DOM 节点。

tag:标记根节点的类型,如 ConcurrentRoot 表示使用并发模式渲染。

hydrate:布尔值,指示是否进行水合操作(用于服务端渲染)。

initialChildren:初始的子元素列表。

hydrationCallbacks:在水合过程中使用的回调函数。

isStrictMode:布尔值,指示是否开启严格模式。

identifierPrefix:用于为 React 元素生成唯一标识符的前缀。

onUncaughtError:未捕获错误的处理回调函数。

onCaughtError:已捕获错误的处理回调函数。

onRecoverableError:可恢复错误的处理回调函数。

transitionCallbacks:用于跟踪过渡状态的回调函数。

formState:表单状态信息。

ts 复制代码
function createFiberRoot(
  containerInfo: Container,// 实际的dom容器
  tag: RootTag,// 渲染模式 并发模式或者传统模式
  hydrate: boolean,
  initialChildren: ReactNodeList,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,// 示范严格模式
  identifierPrefix: string,// 前缀
  onUncaughtError: (
    error: mixed,
    errorInfo: {+componentStack?: ?string},
  ) => void,
  onCaughtError: (
    error: mixed,
    errorInfo: {
      +componentStack?: ?string,
      +errorBoundary?: ?React$Component<any, any>,
    },
  ) => void,
  onRecoverableError: (
    error: mixed,
    errorInfo: {+componentStack?: ?string},
  ) => void,
  transitionCallbacks: null | TransitionTracingCallbacks,
  formState: ReactFormState<any, any> | null,
): FiberRoot {

  // 创建 FiberRootNode 实例
  // 创建一个 FiberRootNode 实例,该实例代表整个 React 应用的根节点,包含了与 DOM 容器、根节点类型、错误处理等相关的信息。
  const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,// 并发模式
    hydrate,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    formState,
  ): any);
  
  if (enableSuspenseCallback) {
    // 设置水合回调函数
    root.hydrationCallbacks = hydrationCallbacks;
  }

  if (enableTransitionTracing) {
    // 设置过渡跟踪回调函数
    root.transitionCallbacks = transitionCallbacks;
  }

  // 创建 Fiber 节点,该节点代表根组件的 Fiber 节点。
  const uninitializedFiber = createHostRootFiber(tag, isStrictMode);

  //root.current 指向当前的 Fiber 节点,建立 FiberRoot 到 Fiber 节点的引用。
  root.current = uninitializedFiber;
  
  //uninitializedFiber.stateNode 指向 FiberRoot,建立 Fiber 节点到 FiberRoot 的引用。
  uninitializedFiber.stateNode = root;

  // 根据是否启用缓存,初始化 Fiber 节点的状态。状态对象包含初始子元素、是否水合以及缓存信息。
  if (enableCache) {
    // 创建缓存对象
    const initialCache = createCache();
    // 保留缓存引用
    retainCache(initialCache);
    
    // 将缓存对象赋值给根节点的 pooledCache 属性
    root.pooledCache = initialCache;

    // 再次保留缓存引用
    retainCache(initialCache);

    // 初始化根 Fiber 节点的状态
    const initialState: RootState = {
      element: initialChildren,//存储初始的子元素列表 initialChildren,这些子元素是 React 应用开始渲染时的初始内容。
      isDehydrated: hydrate,//一个布尔值,用于指示是否处于水合状态,其值来源于 hydrate 参数。
      cache: initialCache,// 存储前面创建的缓存对象 initialCache,这样根 Fiber 节点就可以访问这个缓存对象。
    };

    // 存储前面创建的缓存对象 initialCache,这样根 Fiber 节点就可以访问这个缓存对象。
    // memoizedState 用于存储 Fiber 节点的状态,后续在渲染和更新过程中,React 会根据这个状态来决定如何渲染组件。
    uninitializedFiber.memoizedState = initialState;
  } else {
    const initialState: RootState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: (null: any), // not enabled yet
    };
    uninitializedFiber.memoizedState = initialState;
  }

   // 初始化更新队列
  // 调用 initializeUpdateQueue 函数,为 Fiber 节点初始化更新队列,用于管理后续的状态更新操作。
  initializeUpdateQueue(uninitializedFiber);

  // 返回 FiberRoot 对象
  return root;
}

fiberRoot节点与 fiber节点的关系:
fiberRootcurrent属性 是根节点的fiber对象的引用。

createHostRootFiber 函数

创建一个根 Fiber 节点,该节点代表整个 React 应用的根。在创建过程中,会根据不同的tag模式(如传统模式、并发模式)以及是否开启严格模式和性能分析器等条件,来设置 Fiber 节点的 mode 属性,最后调用 createFiber 函数完成 Fiber 节点的创建。

函数参数含义:

  • tag,类型为 RootTag,用于标记根节点的类型。tag 的值可能为传统模式(值为 0)或并发模式(ConcurrentRoot,值为 1)。
  • isStrictMode,布尔类型,指示是否开启严格模式。严格模式会对组件进行额外的检查,帮助开发者发现潜在问题。
ts 复制代码
function createHostRootFiber(
  tag: RootTag,
  isStrictMode: boolean,
): Fiber {
  
  //设置 mode 属性,用于存储 Fiber 节点的模式。
  let mode;
  //disableLegacyMode常量为true
  if (disableLegacyMode || tag === ConcurrentRoot) {
    mode = ConcurrentMode; // 并发模式
    // 是否严格模式(只有开发环境与藕可能为true)
   // if (isStrictMode === true) {
    //  mode |= StrictLegacyMode | StrictEffectsMode;
   // }
  } else {
 
    mode = NoMode;// NoMode默认为0 即传统模式
  }

  // enableProfilerTimer表示启用性能分析器,isDevToolsPresent表示开发者工具存在
 // if (enableProfilerTimer && isDevToolsPresent) {
       // 相当于  mode = mode | ProfileMode    例如 (1 |2) = 3
 //   mode |= ProfileMode;
 // }

  //创建根 Fiber 节点,HostRoot常量为3
  return createFiber(HostRoot, null, null, mode);
}

initializeUpdateQueue 函数

initializeUpdateQueue函数的功能是为一个 Fiber 节点初始化更新队列。

UpdateQueue<State> 类型的对象 queue,这个对象包含了更新队列的各种属性:

  • baseState:初始状态,赋值为 fiber.memoizedState,memoizedState 存储了当前 Fiber 节点的最新状态。
  • firstBaseUpdatelastBaseUpdate:分别指向更新队列中第一个和最后一个基础更新,初始值都为 null。基础更新是指那些已经被处理过,但由于某些原因(如优先级问题)还没有应用到 baseState 上的更新。
  • shared:是一个包含共享信息的对象,其中:
    • pending:指向待处理的更新链表的尾部,初始值为 null。
    • lanes:表示更新的优先级,初始值为 NoLanes,代表没有任何优先级。
    • hiddenCallbacks:用于存储隐藏的回调函数,初始值为 null。
  • callbacks:用于存储更新完成后的回调函数,初始值为 null。
ts 复制代码
function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
      lanes: NoLanes,
      hiddenCallbacks: null,
    },
    callbacks: null,
  };
  // 将创建好的更新队列 queue 赋值给 fiber 节点的 updateQueue 属性,这样 Fiber 节点就有了自己的更新队列,后续的状态更新操作可以通过这个队列来进行管理。
  fiber.updateQueue = queue;
}

ReactDOMRoot 构造函数

ReactDOMRoot 构造函数的主要作用是创建一个 ReactDOMRoot 实例,并将传入的 FiberRoot 对象存储在实例的 _internalRoot 属性中。

ts 复制代码
function ReactDOMRoot(internalRoot: FiberRoot) {
  this._internalRoot = internalRoot;
}

ReactDOMRoot.prototype.render 函数

renderReactDOMRoot原型上的一个方法,主要作用是在根 DOM 节点上渲染一个 React 元素。

ts 复制代码
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
  // $FlowFixMe[missing-this-annot]
  function (children: ReactNodeList): void {
    const root = this._internalRoot;
    if (root === null) {
      throw new Error('Cannot update an unmounted root.');
    }
    //执行更新
    updateContainer(children, root, null, null);
  };

ReactDOMRoot.prototype.unmount 函数

unmount 方法是ReactDOMRoot原型上的一个方法,其主要作用是卸载 React 应用的根节点,清理相关资源,将根节点从 DOM 中移除,释放内存并确保应用不再占用相关资源。

ts 复制代码
ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount =
  // $FlowFixMe[missing-this-annot]
  function (): void {
    // 获取内部根节点实例
    const root = this._internalRoot;
    // 存在
    if (root !== null) {
      // 清空根实例的引用
      this._internalRoot = null;
      // root.containerInfo 存储了 React 应用渲染的 DOM 容器节点信息。通过 root.containerInfo 获取根节点对应的 DOM 容器节点。
      const container = root.containerInfo;

      // 同步更新容器
      updateContainerSync(null, root, null, null);

      // 刷新同步工作
      flushSyncWork();
      // 取消容器的根节点标记
      unmarkContainerAsRoot(container);
    }
  };

全局变量

ts 复制代码
const randomKey = Math.random().toString(36).slice(2);

//internalInstanceKey 用于在 DOM 元素上存储 React Fiber 实例的相关信息。
const internalInstanceKey = '__reactFiber$' + randomKey;

// internalPropsKey 用于存储传递给组件的 props 信息。
const internalPropsKey = '__reactProps$' + randomKey;

const internalContainerInstanceKey = '__reactContainer$' + randomKey;

// internalEventHandlersKey 用于存储事件处理函数的相关信息。
const internalEventHandlersKey = '__reactEvents$' + randomKey;
const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
const internalEventHandlesSetKey = '__reactHandles$' + randomKey;
const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
const internalHoistableMarker = '__reactMarker$' + randomKey;

工具函数 markContainerAsRoot

markContainerAsRoot的作用是将一个 DOM 容器节点标记为 React 应用的根容器,并将对应的 Fiber 根节点与该容器节点关联起来。在 React 的渲染过程中,需要将 Fiber 树与实际的 DOM 节点进行关联。

函数参数含义:

  • hostRoot, 根节点fiber
  • node, DOM 容器节点
ts 复制代码
function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
  // $FlowFixMe[prop-missing]
  node[internalContainerInstanceKey] = hostRoot;
  // 变量internalContainerInstanceKey = '__reactContainer$' + randomKey;
}

工具函数 markContainerAsRoot

ts 复制代码
function unmarkContainerAsRoot(node: Container): void {
  // $FlowFixMe[prop-missing]
  node[internalContainerInstanceKey] = null;
}

流程图

后续继续看FiberRoot节点和Fiber节点的结构以及root.render中的updateContainer是如何执行的。

相关推荐
crary,记忆12 小时前
微前端 - Module Federation使用完整示例
前端·react·angular
aiguangyuan19 小时前
浅谈 React Hooks
react·前端开发
whatever who cares3 天前
React hook之userReducer
react.js·react
aiguangyuan3 天前
React Hooks 基础指南
react·前端开发
aiguangyuan3 天前
React 项目初始化与搭建指南
react·前端开发
aiguangyuan4 天前
React 组件异常捕获机制详解
react·前端开发
aiguangyuan4 天前
深入理解 JSX:React 的核心语法
react·前端开发
aiguangyuan4 天前
React 基础语法
react·前端开发
aiguangyuan6 天前
React 核心概念与生态系统
react·前端开发
aiguangyuan6 天前
React 18 生命周期详解与并发模式下的变化
react·前端开发