三、深入浅出React源码:关键入口函数解析-createRoot

208 阅读2分钟

一、根容器创建解析

考虑下面的一个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 容器中。以下是该函数的主要作用和步骤:

  1. 参数验证:首先,它检查传入的 container 是否是一个有效的 DOM 元素。如果不是,则抛出一个错误。

  2. 开发环境警告:在开发环境中,如果用户的选项中包含 hydrate,会发出警告通知用户使用 hydrateRoot(container, ) 是推荐的方式。同样地,如果用户错误地传递了一个 JSX 元素给 createRoot 而不是配置对象,函数也会输出错误信息提示用户正确的使用方法。

  3. 读取和设置选项:根据 options 对象的不同属性值,函数设置内部的变量如 isStrictMode、identifierPrefix、以及错误处理回调函数 onUncaughtError、onCaughtError 和 onRecoverableError。这些设置将影响新创建的根容器的行为。

  4. 创建容器:调用 createContainer 函数,创建一个 React 容器实例。该容器将负责将虚拟 DOM(即组件树)与真实 DOM 对接起来。

  5. 标记容器为根节点:调用 markContainerAsRoot 函数将创建的根容器标记在真实的 DOM 结构上,以便 React 能够识别和管理。

  6. 事件监听:为根容器所处的 DOM 节点(如果是注释节点,则为父节点)设置响应所有支持事件的监听器。

  7. 返回根对象:最后,函数返回一个新的 ReactDOMRoot 对象,通过该对象的 render 方法可以开始组件的渲染过程。

整体而言,createRoot 是 React 新版本中用于挂载 React 应用到 DOM 的推荐方式,它提供了更多的配置选项和更高的灵活性。