🔥🔥🔥 React18 源码学习 - 容器的挂载

前言

本文的React代码版本为18.2.0

可调试的代码仓库为:GitHub - yyyao-hh/react-debug at master-pure

当我们初次学习React时,第一个接触的API往往是ReactDOM.render。这个方法是React应用与浏览器 DOM连接的桥梁,也是整个应用渲染的起点。理解它的实现原理能为后续理解组件生命周期、虚拟DOM和协调算法打下坚实基础。

ReactDOM.render 的基本调用

在React应用中,我们通常这样使用ReactDOM.render:

  • React17及之前:首先获取一个DOM容器,然后将应用进行挂载。
javascript 复制代码
/* src/index.js */

import ReactDOM from 'react-dom';
import App from './App';

const root = document.getElementById('root');
ReactDOM.render(<App />, root);
  • React18开始:首先需要创建一个能够支持并发特性的root对象,然后将应用进行挂载。
javascript 复制代码
/* src/index.js */

import * as ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

ReactDOM.createRoot的作用便是创建React根节点:作为一个长期存在的、用于管理该容器内整个React树的控制中心。接下来将围绕这个函数进行深入分析!

ReactDOM.render在React18中已被标记为"废弃"(legacy)。它创建的根节点运行在遗留模式(Legacy Mode)下,其渲染过程是同步、不可中断的。

而ReactDOM.createRoot创建的根节点,则默认启用了并发模式(Concurrent Mode)。它允许React在准备渲染更新时进行中断、暂停和恢复,将主线程的控制权交还给浏览器以优先处理高优先级的用户交互(如输入、动画),从而带来更流畅的用户体验。

ReactDOM.createRoot:构建根容器

我们现在开始深入了解下ReactDOM.createRoot这个函数:

javascript 复制代码
/* src/index.js */

import ReactDOM from 'react-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));

在深入阅读源码后我们会了解到,ReactDOM.createRoot通过层层调用最终停在了某个createRoot方法中,这时的createRoot主要做了三件事:

  1. createContainer: 创建了根容器对象FiberRoot
  2. listenToAllSupportedEvents: 代理了所有事件,这是React合成事件系统的基石
  3. new ReactDOMRoot: 创建ReactDOMRoot实例并返回。该实例对外暴露了render和unmount方法
scss 复制代码
/* src/react/packages/react-dom/client.js */
export function createRoot(...): RootType {
  return createRootImpl(...);
}

/* src/react/packages/react-dom/src/client/ReactDOM.js */
function createRoot(...): RootType {
  return createRootImpl(...);
}

/* src/react/packages/react-dom/src/client/ReactDOMRoot.js */
export function createRoot(...): RootType {
  // 1. 创建容器对象 FiberRoot
  const root = createContainer(...);

  // 2. 代理所有事件
  listenToAllSupportedEvents(rootContainerElement);

  // 3. 返回公开的根对象
  return new ReactDOMRoot(root);
}

然后我们看到createContainer函数中又继续调用了createFiberRoot函数。在这个函数的内部依旧做了三件比较关键的事:

  1. 创建 FiberRootNode
  2. 创建 RootFiberNode
  3. 进行关联,二者通过.current和.stateNode互相引用,形成闭环:
arduino 复制代码
/* src/react/packages/react-reconciler/src/ReactFiberReconciler.old.js */
export function createContainer(...): OpaqueRoot {
  return createFiberRoot(...);
}

/* src/react/packages/react-reconciler/src/ReactFiberRoot.old.js */
export function createFiberRoot(...): FiberRoot {
  // 1. 创建 FiberRoot
  const root: FiberRoot = (new FiberRootNode(...));

  // 2. 创建 RootFiber
  const uninitializedFiber = createHostRootFiber(...);
  
  // 3. 将二者进行关联
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  return root;
}

看到这里大家肯定对这新出现的结构一脸懵 o((⊙﹏⊙))o

  • FiberRootNode :Fiber树的容器,它代表了整个React应用的根节点,与实际的DOM容器(例如div#root)相关联。每个React应用通常只有一个FiberRoot
  • RootFiberNode :Fiber树的根节点,即HostRoot。它是Fiber树结构的起点,其子节点就是应用顶层组件(例如<App />)

最后顺便看一下用于生成RootFiber的函数:createHostRootFiber。它通过调用函数createFiber并传入HostRoot参数,生成了一个代表根节点的Fiber。

这里还有很多Fiber类型,会在Fiber相关的章节详细讲到

javascript 复制代码
/* src/react/packages/react-reconciler/src/ReactWorkTags.js */
export const FunctionComponent = 0; // 函数组件
export const ClassComponent = 1;    // 类组件
export const HostRoot = 3;          // 根节点

/* src/react/packages/react-reconciler/src/ReactFiber.old.js */
export function createHostRootFiber(...): Fiber {
  return createFiber(HostRoot, null, null, mode);
}

const createFiber = function(...): Fiber {
  return new FiberNode(...);
}

总结

本文从宏观角度解析了ReactDOM.createRoot如何为React应用初始化容器、创建核心数据结构,并最终准备好渲染的舞台。理解这一过程是掌握React并发渲染模型(Concurrent Mode)的基础。

下一章我们将学习另一个重要的数据结构:Fiber

相关推荐
znsoft6 小时前
MaClaw(码卡龙)技术拆解:一个 Go + React 的可自进化智能体平台
开发语言·react.js·golang
濮水大叔10 小时前
React/Next.js 生态里,有没有 CabloyJS 这样的全栈框架?
react.js·全栈·next.js
前端小D11 小时前
React Native Android 项目搭建
android·react native·react.js
Highcharts.js2 天前
Highcharts 集成到 React 或 Vue 项目中时,如何高效地使用 TypeScript技巧?
vue.js·人工智能·react.js·typescript·前端框架·highcharts
RobinXYuan2 天前
【Kafka 源码手记】05 Broker 网络层与请求处理(中):Processor 事件循环与线上的字节
kafka·源码阅读
liuchangng3 天前
React 19 + Vite 6 实战:从教材 PDF 到 866 词间隔重复单词 App
前端·react.js·pdf·vite·开源项目
鸿蒙开发3 天前
Three.js 首帧卡顿排查:compileAsync 之后,为什么还会停一下?
react.js·three.js
小李科技3 天前
【性能优化】事件委托:目标定位与 React/Vue 实战
前端·vue.js·react.js·面试·性能优化·前端框架
csj503 天前
前端基础之《React(16)—Modal组件封装》
前端·react.js
ZoeLandia6 天前
React11:数据请求与 TanStack Query
前端·react.js·typescript·react