一、根容器创建解析
考虑下面的一个React 应用
import { createRoot } from 'react-dom/client';
// Clear the existing HTML content
document.body.innerHTML = '<div id="app"></div>';
// Render your React component instead
const root = createRoot(document.getElementById('app'));
root.render(<h1>Hello, world</h1>);
我们从react-dom的client模块里面,导出了一个createRoot函数,然后使用这个函数创建了一个根节点root,通过根节点渲染了一段jsx,这是我们很常见的一段React初始化应用的代码,接下来我们深入探讨一下,当我们调用createRoot的时候,里面发生了什么事情
二、 createRoot 源码解析
createRoot 函数位于文件 ReactDOMRoot文件中,原始代码如下所示
export function createRoot(
container: Element | Document | DocumentFragment,
options ? : CreateRootOptions,
): RootType {
/
if (!isValidContainer(container)) {
throw new Error('Target container is not a DOM element.');
}
warnIfReactDOMContainerInDEV(container);
const concurrentUpdatesByDefaultOverride = false;
let isStrictMode = false;
let identifierPrefix = '';
let onUncaughtError = defaultOnUncaughtError;
let onCaughtError = defaultOnCaughtError;
let onRecoverableError = defaultOnRecoverableError;
let transitionCallbacks = null;
if (options !== null && options !== undefined) {
if (__DEV__) {
if ((options: any).hydrate) {
console.warn(
'hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead.',
);
} else {
if (
typeof options === 'object' &&
options !== null &&
(options: any).$$typeof === REACT_ELEMENT_TYPE
) {
console.error(
'You passed a JSX element to createRoot. You probably meant to ' +
'call root.render instead. ' +
'Example usage:\n\n' +
' let root = createRoot(domContainer);\n' +
' root.render(<App />);',
);
}
}
}
if (options.unstable_strictMode === true) {
isStrictMode = true;
}
if (options.identifierPrefix !== undefined) {
identifierPrefix = options.identifierPrefix;
}
if (options.onUncaughtError !== undefined) {
onUncaughtError = options.onUncaughtError;
}
if (options.onCaughtError !== undefined) {
onCaughtError = options.onCaughtError;
}
if (options.onRecoverableError !== undefined) {
onRecoverableError = options.onRecoverableError;
}
if (options.unstable_transitionCallbacks !== undefined) {
transitionCallbacks = options.unstable_transitionCallbacks;
}
}
const root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onUncaughtError,
onCaughtError,
onRecoverableError,
transitionCallbacks,
);
markContainerAsRoot(root.current, container);
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE ?
(container.parentNode: any) :
container;
listenToAllSupportedEvents(rootContainerElement);
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
return new ReactDOMRoot(root);
}
这个函数 createRoot 是 React 中用于创建一个 React 根容器的方法,主要用于客户端渲染(Client Rendering)。它返回一个 RootType 对象,该对象提供了一个 render 方法,用于将 React 组件渲染到指定的 DOM 容器中。以下是该函数的主要作用和步骤:
-
参数验证:首先,它检查传入的 container 是否是一个有效的 DOM 元素。如果不是,则抛出一个错误。
-
开发环境警告:在开发环境中,如果用户的选项中包含 hydrate,会发出警告通知用户使用 hydrateRoot(container, ) 是推荐的方式。同样地,如果用户错误地传递了一个 JSX 元素给 createRoot 而不是配置对象,函数也会输出错误信息提示用户正确的使用方法。
-
读取和设置选项:根据 options 对象的不同属性值,函数设置内部的变量如 isStrictMode、identifierPrefix、以及错误处理回调函数 onUncaughtError、onCaughtError 和 onRecoverableError。这些设置将影响新创建的根容器的行为。
-
创建容器:调用 createContainer 函数,创建一个 React 容器实例。该容器将负责将虚拟 DOM(即组件树)与真实 DOM 对接起来。
-
标记容器为根节点:调用 markContainerAsRoot 函数将创建的根容器标记在真实的 DOM 结构上,以便 React 能够识别和管理。
-
事件监听:为根容器所处的 DOM 节点(如果是注释节点,则为父节点)设置响应所有支持事件的监听器。
-
返回根对象:最后,函数返回一个新的 ReactDOMRoot 对象,通过该对象的 render 方法可以开始组件的渲染过程。
整体而言,createRoot 是 React 新版本中用于挂载 React 应用到 DOM 的推荐方式,它提供了更多的配置选项和更高的灵活性。