前言
本文的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主要做了三件事:
createContainer: 创建了根容器对象FiberRootlistenToAllSupportedEvents: 代理了所有事件,这是React合成事件系统的基石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函数。在这个函数的内部依旧做了三件比较关键的事:
- 创建
FiberRootNode - 创建
RootFiberNode - 进行关联,二者通过
.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