跳到主要内容

React DOM root

一、作用

二、导出的类型

1. 根类型

import type { ReactNodeList } from 'shared/ReactTypes';
import type {
FiberRoot,
TransitionTracingCallbacks,
} from 'react-reconciler/src/ReactInternalTypes';

// 根类型
export type RootType = {
render(children: ReactNodeList): void;
unmount(): void;
_internalRoot: FiberRoot | null;
};

// 创建根选项
export type CreateRootOptions = {
unstable_strictMode?: boolean;
unstable_transitionCallbacks?: TransitionTracingCallbacks;
identifierPrefix?: string;
onUncaughtError?: (
error: mixed,
// errorInfo: {+componentStack?: ?string},
errorInfo: { componentStack?: ?string },
) => void;
onCaughtError?: (
error: mixed,
errorInfo: {
// +componentStack?: ?string,
componentStack?: ?string;
// +errorBoundary?: ?component(...props: any),
errorBoundary?: ?Component;
},
) => void;
onRecoverableError?: (
error: mixed,
// errorInfo: {+componentStack?: ?string},
errorInfo: { componentStack?: ?string },
) => void;
onDefaultTransitionIndicator?: () => void | (() => void);
};

// 补水根选项
export type HydrateRootOptions = {
// Hydration options
// 补水选项
onHydrated?: (hydrationBoundary: Comment) => void;
onDeleted?: (hydrationBoundary: Comment) => void;
// Options for all roots
// 所有根的选项
unstable_strictMode?: boolean;
unstable_transitionCallbacks?: TransitionTracingCallbacks;
identifierPrefix?: string;
onUncaughtError?: (
error: mixed,
// errorInfo: {+componentStack?: ?string},
errorInfo: { componentStack?: ?string },
) => void;
onCaughtError?: (
error: mixed,
errorInfo: {
// +componentStack?: ?string,
componentStack?: string;
// +errorBoundary?: ?component(...props: any),
errorBoundary?: ?Component;
},
) => void;
onRecoverableError?: (
error: mixed,
// errorInfo: {+componentStack?: ?string},
errorInfo: { componentStack?: ?string },
) => void;
onDefaultTransitionIndicator?: () => void | (() => void);
formState?: ReactFormState<any, any> | null;
};

三、创建根

备注
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 onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator;
let transitionCallbacks = null;

if (options !== null && options !== undefined) {
if (__DEV__) {
if ((options as any).hydrate) {
console.warn(
'hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead.',
);
} else {
if (
typeof options === 'object' &&
options !== null &&
(options as 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 (enableDefaultTransitionIndicator) {
if (options.onDefaultTransitionIndicator !== undefined) {
onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
}
}
if (options.unstable_transitionCallbacks !== undefined) {
transitionCallbacks = options.unstable_transitionCallbacks;
}
}

const root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onUncaughtError,
onCaughtError,
onRecoverableError,
onDefaultTransitionIndicator,
transitionCallbacks,
);
markContainerAsRoot(root.current, container);

const rootContainerElement: Document | Element | DocumentFragment =
!disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE
? (container.parentNode as any)
: container;
listenToAllSupportedEvents(rootContainerElement);

return new ReactDOMRoot(root);
}

四、补水根

备注
export function hydrateRoot(
container: Document | Element,
initialChildren: ReactNodeList,
options?: HydrateRootOptions,
): RootType {
if (!isValidContainer(container)) {
throw new Error('Target container is not a DOM element.');
}

warnIfReactDOMContainerInDEV(container);

if (__DEV__) {
if (initialChildren === undefined) {
console.error(
'Must provide initial children as second argument to hydrateRoot. ' +
'Example usage: hydrateRoot(domContainer, <App />)',
);
}
}

// For now we reuse the whole bag of options since they contain
// the hydration callbacks.
// 目前我们重用整包选项,因为它们包含水化回调。
const hydrationCallbacks = options != null ? options : null;

const concurrentUpdatesByDefaultOverride = false;
let isStrictMode = false;
let identifierPrefix = '';
let onUncaughtError = defaultOnUncaughtError;
let onCaughtError = defaultOnCaughtError;
let onRecoverableError = defaultOnRecoverableError;
let onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator;
let transitionCallbacks = null;
let formState = null;
if (options !== null && options !== undefined) {
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 (enableDefaultTransitionIndicator) {
if (options.onDefaultTransitionIndicator !== undefined) {
onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
}
}
if (options.unstable_transitionCallbacks !== undefined) {
transitionCallbacks = options.unstable_transitionCallbacks;
}
if (options.formState !== undefined) {
formState = options.formState;
}
}

const root = createHydrationContainer(
initialChildren,
null,
container,
ConcurrentRoot,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onUncaughtError,
onCaughtError,
onRecoverableError,
onDefaultTransitionIndicator,
transitionCallbacks,
formState,
);
markContainerAsRoot(root.current, container);
// This can't be a comment node since hydration doesn't work on comment nodes anyway.
// 这不能是一个注释节点,因为水合在注释节点上无论如何都不起作用。
listenToAllSupportedEvents(container);

return new ReactDOMHydrationRoot(root);
}

五、工具

1. React DOM 根

备注
function ReactDOMRoot(internalRoot: FiberRoot) {
this._internalRoot = internalRoot;
}

ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
function (children: ReactNodeList): void {
const root = this._internalRoot;
if (root === null) {
throw new Error('Cannot update an unmounted root.');
}

if (__DEV__) {
// using a reference to `arguments` bails out of GCC optimizations which
// affect function arity
// 使用对 `arguments` 的引用会使 GCC 的优化失效,这会影响函数的参数个数
const args = arguments;
if (typeof args[1] === 'function') {
console.error(
'does not support the second callback argument. ' +
'To execute a side effect after rendering, declare it in a component body with useEffect().',
);
} else if (isValidContainer(args[1])) {
console.error(
'You passed a container to the second argument of root.render(...). ' +
"You don't need to pass it again since you already passed it to create the root.",
);
} else if (typeof args[1] !== 'undefined') {
console.error(
'You passed a second argument to root.render(...) but it only accepts ' +
'one argument.',
);
}
}
updateContainer(children, root, null, null);
};

ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount =
function (): void {
if (__DEV__) {
// using a reference to `arguments` bails out of GCC optimizations which
// affect function arity
// 使用对 `arguments` 的引用会使 GCC 的优化失效,这会影响函数的参数个数
const args = arguments;
if (typeof args[0] === 'function') {
console.error(
'does not support a callback argument. ' +
'To execute a side effect after rendering, declare it in a component body with useEffect().',
);
}
}
const root = this._internalRoot;
if (root !== null) {
this._internalRoot = null;
const container = root.containerInfo;
if (__DEV__) {
if (isAlreadyRendering()) {
console.error(
'Attempted to synchronously unmount a root while React was already ' +
'rendering. React cannot finish unmounting the root until the ' +
'current render has completed, which may lead to a race condition.',
);
}
}
updateContainerSync(null, root, null, null);
flushSyncWork();
unmarkContainerAsRoot(container);
}
};

2. React DOM 水合根

备注
function ReactDOMHydrationRoot(internalRoot: FiberRoot) {
this._internalRoot = internalRoot;
}

// 安排补水
function scheduleHydration(target: Node) {
if (target) {
queueExplicitHydrationTarget(target);
}
}

ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = scheduleHydration;

3. 在开发环境中警告 ReactDOM 容器

备注
function warnIfReactDOMContainerInDEV(container: any) {
if (__DEV__) {
if (isContainerMarkedAsRoot(container)) {
if (container._reactRootContainer) {
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.',
);
}
}
}
}