react源码解读(1) 入口函数createRoot,render

92 阅读2分钟

使用的源码为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

image.png 上图是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.',
        );
      }
    }
  }
}