使用的源码为react18.1.0版本,demo代码如下
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
const root = document.getElementById('root')
ReactDOM.createRoot(root).render(<App />);
作为专栏的第一篇,本文主要解析createRoot和render的执行过程,上面的代码是一个最简单的使用react例子,下文是对createRoot和render的分析
createRoot
上图是createRoot的调用栈,完整源码如下, 源码后是讲解
//代码位置react-dom/src/client/ReactDOM.js
function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
console.log('createRoot1调用')
if (__DEV__) {
if (!Internals.usingClientEntryPoint && !__UMD__) {
console.error(
'You are importing createRoot from "react-dom" which is not supported. ' +
'You should instead import it from "react-dom/client".',
);
}
}
// import {
// createRoot as createRootImpl,
// hydrateRoot as hydrateRootImpl,
// isValidContainer,
// } from './ReactDOMRoot';
return createRootImpl(container, options);
}
//react-dom/src/client/ReactDOMRoot.js
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
console.log('react入口函数地址src\react\v18\react-dom\src\client\ReactDOMRoot.js')
// 判断是否是dom节点
if (!isValidContainer(container)) {
throw new Error('createRoot(...): Target container is not a DOM element.');
}
//在body节点创建react根节点,或者在相同容器多次调用createRoot()方法会触发console.error
warnIfReactDOMContainerInDEV(container);
let isStrictMode = false;
let concurrentUpdatesByDefaultOverride = false;
let identifierPrefix = '';
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 (
allowConcurrentByDefault &&
options.unstable_concurrentUpdatesByDefault === true
) {
concurrentUpdatesByDefaultOverride = true;
}
if (options.identifierPrefix !== undefined) {
identifierPrefix = options.identifierPrefix;
}
if (options.onRecoverableError !== undefined) {
onRecoverableError = options.onRecoverableError;
}
if (options.transitionCallbacks !== undefined) {
transitionCallbacks = options.transitionCallbacks;
}
}
const root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
markContainerAsRoot(root.current, container);
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container;
listenToAllSupportedEvents(rootContainerElement);
return new ReactDOMRoot(root);
}
1.isValidContainer
来看createRoot的执行过程,他会先调用isValidContainer方法判断入参container是否为一个dom元素,下面是isValidContainer的代码
export const ELEMENT_NODE = 1;
export const TEXT_NODE = 3;
export const COMMENT_NODE = 8;
export const DOCUMENT_NODE = 9;
export const DOCUMENT_FRAGMENT_NODE = 11;
export function isValidContainer(node: any): boolean {
// 判断节点属性 https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(!disableCommentsAsDOMContainers &&
node.nodeType === COMMENT_NODE &&
(node: any).nodeValue === ' react-mount-point-unstable '))
);
}
入参应该为一个dom节点,通过nodeType属性去判断,参考developer.mozilla.org/zh-CN/docs/… 可以看到,react支持的nodeType值1,8,9,11分别为
ELEMENT_NODE 一个元素节点,例如 <p> 和 <div>。
COMMENT_NODE 一个注释节点,例如 <!-- … -->。
DOCUMENT_NODE 一个 Document 节点。
DOCUMENT_FRAGMENT_NODE 一个 DocumentFragment 节点
2.warnIfReactDOMContainerInDEV
接下来会执行warnIfReactDOMContainerInDEV方法,这里只会console.error,如果在在body节点创建react根节点,或者在相同容器多次调用createRoot()方法会触发.
function warnIfReactDOMContainerInDEV(container: any) {
if (__DEV__) {
if (
container.nodeType === ELEMENT_NODE &&
((container: any): Element).tagName &&
((container: any): Element).tagName.toUpperCase() === 'BODY'
) {
// 再body上创建根节点
console.error(
'createRoot(): Creating roots directly with document.body is ' +
'discouraged, since its children are often manipulated by third-party ' +
'scripts and browser extensions. This may lead to subtle ' +
'reconciliation issues. Try using a container element created ' +
'for your app.',
);
}
if (isContainerMarkedAsRoot(container)) {
if (container._reactRootContainer) {
// 这个警告是因为在相同的容器上多次调用createRoot()方法
console.error(
'You are calling ReactDOMClient.createRoot() on a container that was previously ' +
'passed to ReactDOM.render(). This is not supported.',
);
} else {
console.error(
'You are calling ReactDOMClient.createRoot() on a container that ' +
'has already been passed to createRoot() before. Instead, call ' +
'root.render() on the existing root instead if you want to update it.',
);
}
}
}
}