likes
comments
collection
share

react源码系列(一)-createRoot做了些什么

作者站长头像
站长
· 阅读数 4

大家好,我是无问,今天正式开更react源码系列,我会从最开始的源码讲起,附带源码所在文件的路径,大家可以很方便的找到函数所在的位置,并将函数的主要实现粘贴出来方便大家查看;另外可以使用目录形式进行快速查找函数调用;一个主要函数的实现为一个系列进行更新,文末附上总结;如果帮助了你,请点个赞吧!👍👍👍

react源码系列(一)-createRoot做了些什么

首先调用creatRoot函数:

// 获取根元素
const root = document.getElementById("root");

// Concurrent mode
// 使用ReactDOM的createRoot方法渲染App组件
ReactDOM.createRoot(root).render(<App />);

createRoot

function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  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".',
      );
    }
  }
  return createRootImpl(container, options);
}

createRootImpl-->createRoot

import {
  createRoot as createRootImpl,
  hydrateRoot as hydrateRootImpl,
  isValidContainer,
} from './ReactDOMRoot';

所以createRoot只是重新命名为createRootImpl;

createRoot

先看下定义:

export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType

type RootType = {  
render(children: ReactNodeList): void;  
unmount(): void;  
_internalRoot: any;  
}

看下内部实现:

export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  if (!isValidContainer(container)) {
    throw new Error('createRoot(...): Target container is not a DOM element.');
  }

  warnIfReactDOMContainerInDEV(container);

  let isStrictMode = false;
  let concurrentUpdatesByDefaultOverride = false;
  let identifierPrefix = '';
  let onRecoverableError = defaultOnRecoverableError;
  let transitionCallbacks = null;
  //....
  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);
}

内部调用了几个方法:

createRoot-->isValidContainer

export function isValidContainer(node: any): boolean {
  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 '))
  );
}

这段代码的目的是检查一个给定的节点(node)是否是一个有效的DOM容器节点。根据节点的类型和一些特殊条件,代码会返回一个布尔值。

createRoot-->warnIfReactDOMContainerInDEV

src\react\v18\react-dom\src\client\ReactDOMRoot.js

function warnIfReactDOMContainerInDEV(container: any) {
  if (__DEV__) {
    // 检查容器是否为 body 元素
    if (
      container.nodeType === ELEMENT_NODE &&
      ((container: any): Element).tagName &&
      ((container: any): Element).tagName.toUpperCase() === '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) {
        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.',
        );
      }
    }
  }
}

这段代码是用于警告在开发环境中创建 React 根元素的一些潜在问题。实现原理是通过检查传入的容器是否为 body 元素以及是否已经标记为根元素来 warn 开发者。这些警告有助于开发者避免在开发过程中的一些潜在问题。

用途:

  • 在开发过程中,警告开发者避免在 DOM 中直接创建根元素,因为外部脚本和扩展可能会 manipulated 它。
  • 警告开发者,不要重复调用 ReactDOMClient.createRoot() on 同一个容器,因为这会导致不一致的行为。

createRoot-->warnIfReactDOMContainerInDEV-->isContainerMarkedAsRoot

src\react\v18\react-dom\src\client\ReactDOMRoot.js 检查node有没有internalContainerInstanceKey属性,没有返回false 有返回true

var randomKey = Math.random().toString(36).slice(2);
var internalContainerInstanceKey = '__reactContainere$' + randomKey;
function isContainerMarkedAsRoot(node) {
  return !!node[internalContainerInstanceKey];
} 

createRoot-->createContainer

src\react\v18\react-reconciler\src\ReactFiberReconciler.old.js

/ 导出一个函数,用于创建容器
export function createContainer(
  // 接收一个容器信息参数
  containerInfo: Container,
  // 接收一个根标签参数
  tag: RootTag,
  // 接收一个挂载回调参数
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  // 接收一个严格模式参数
  isStrictMode: boolean,
  // 接收一个并发更新默认值参数
  concurrentUpdatesByDefaultOverride: null | boolean,
  // 接收一个标识前缀参数
  identifierPrefix: string,
  // 接收一个恢复错误回调参数
  onRecoverableError: (error: mixed) => void,
  // 接收一个过渡回调参数
  transitionCallbacks: null | TransitionTracingCallbacks
): OpaqueRoot {
  // 设置挂载为false
  const hydrate = false;
  // 设置初始子节点为null
  const initialChildren = null;
  // 调用创建虚拟根节点的函数
  return createFiberRoot(
    // 传入容器信息
    containerInfo,
    // 传入根标签
    tag,
    // 传入挂载
    hydrate,
    // 传入初始子节点
    initialChildren,
    // 传入挂载回调
    hydrationCallbacks,
    // 传入严格模式
    isStrictMode,
    // 传入并发更新默认值
    concurrentUpdatesByDefaultOverride,
    // 传入标识前缀
    identifierPrefix,
    // 传入恢复错误回调
    onRecoverableError,
    // 传入过渡回调
    transitionCallbacks
  );
}

createContainer-->createFiberRoot

src\react\v18\react-reconciler\src\ReactFiberRoot.old.js 可以看到createContainer内又调用了createFiberRoot,并返回结果;

(alias) type FiberRoot = {  
BaseFiberRootProperties: any;  
SuspenseCallbackOnlyFiberRootProperties: any;  
UpdaterTrackingOnlyFiberRootProperties: any;  
TransitionTracingOnlyFiberRootProperties: any;  
}

export function createFiberRoot(
  containerInfo: any,    //就是div#root
  tag: RootTag,          //1
  hydrate: boolean,     //false
  initialChildren: ReactNodeList,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onRecoverableError: null | ((error: mixed) => void),
  transitionCallbacks: null | TransitionTracingCallbacks
): FiberRoot {
//FiberRootNode用作构造函数,创建了root实例
  const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError
  ): any);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  if (enableTransitionTracing) {
    root.transitionCallbacks = transitionCallbacks;
  }

  // Cyclic construction. This cheats the type system right now because
  // stateNode is any.
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride
  );
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;
    //判断是否能用缓存  在初始化FiberRoot.memoizedState的时候会有缓存上的区别
  if (enableCache) {
    const initialCache = createCache();
    retainCache(initialCache);
    root.pooledCache = initialCache;
    retainCache(initialCache);
    //这段代码是用于初始化一个Fiber节点的状态。Fiber是React内部用于跟踪组件和渲染过程中的状态。
    const initialState: RootState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: initialCache,
      transitions: null,
      pendingSuspenseBoundaries: null,
    };
    uninitializedFiber.memoizedState = initialState;
  } else {
    const initialState: RootState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: (null: any), // not enabled yet
      transitions: null,
      pendingSuspenseBoundaries: null,
    };
    uninitializedFiber.memoizedState = initialState;
  }
    //传入的是FiberNode
  initializeUpdateQueue(uninitializedFiber);

  return root;
}

在createFiberRoot中又调用了几个方法:

createFiberRoot-->FiberRootNode

可以看到FiberRootNode基本上就是对各种属性做了初始化:

function FiberRootNode(
  containerInfo,
  tag,
  hydrate,
  identifierPrefix,
  onRecoverableError
) {
  // 设置标签
  this.tag = tag;
  // 设置容器信息
  this.containerInfo = containerInfo;
  // 设置待处理子节点
  this.pendingChildren = null;
  // 设置当前节点
  this.current = null;
  // 设置缓存节点
  this.pingCache = null;
  // 设置已完成工作
  this.finishedWork = null;
  // 设置超时处理
  this.timeoutHandle = noTimeout;
  // 设置上下文
  this.context = null;
  // 设置待处理上下文
  this.pendingContext = null;
  // 设置回调节点
  this.callbackNode = null;
  // 设置回调优先级
  this.callbackPriority = NoLane;
  // 设置事件时间戳
  this.eventTimes = createLaneMap(NoLanes);
  // 设置过期时间戳
  this.expirationTimes = createLaneMap(NoTimestamp);

  // 设置待处理通道
  this.pendingLanes = NoLanes;
  // 设置暂停通道
  this.suspendedLanes = NoLanes;
  // 设置已ping的通道
  this.pingedLanes = NoLanes;
  // 设置过期的通道
  this.expiredLanes = NoLanes;
  // 设置可读的通道
  this.mutableReadLanes = NoLanes;
  // 设置已完成的通道
  this.finishedLanes = NoLanes;

  // 设置 entangledLanes
  this.entangledLanes = NoLanes;
  // 设置 entanglements
  this.entanglements = createLaneMap(NoLanes);

  // 设置标识符前缀
  this.identifierPrefix = identifierPrefix;
  // 设置可恢复的错误处理
  this.onRecoverableError = onRecoverableError;

  // 如果有缓存功能,则初始化缓存相关变量
  if (enableCache) {
    this.pooledCache = null;
    this.pooledCacheLanes = NoLanes;
  }

  // 如果有支持热重载功能,则初始化热重载相关变量
  if (supportsHydration) {
    this.mutableSourceEagerHydrationData = null;
  }

  // 如果有回调功能,则初始化回调相关变量
  if (enableSuspenseCallback) {
    this.hydrationCallbacks = null;
  }

  // 如果有过渡追踪功能,则初始化过渡追踪相关变量
  if (enableTransitionTracing) {
    this.transitionCallbacks = null;
    const transitionLanesMap = (this.transitionLanes = []);
    for (let i = 0; i < TotalLanes; i++) {
      transitionLanesMap.push(null);
    }
  }

  // 如果有性能计数器功能,则初始化性能计数器相关变量
  if (enableProfilerTimer && enableProfilerCommitHooks) {
    this.effectDuration = 0;
    this.passiveEffectDuration = 0;
  }

  // 如果有更新跟踪功能,则初始化更新跟踪相关变量
  if (enableUpdaterTracking) {
    this.memoizedUpdaters = new Set();
    const pendingUpdatersLaneMap = (this.pendingUpdatersLaneMap = []);
    for (let i = 0; i < TotalLanes; i++) {
      pendingUpdatersLaneMap.push(new Set());
    }
  }

  // 如果是开发模式,则根据tag初始化调试相关变量
  if (__DEV__) {
    switch (tag) {
      case ConcurrentRoot:
        this._debugRootType = hydrate ? "hydrateRoot()" : "createRoot()";
        break;
      case LegacyRoot:
        this._debugRootType = hydrate ? "hydrate()" : "render()";
        break;
    }
  }
}

createFiberRoot-->createHostRootFiber

src\react\v18\react-reconciler\src\ReactFiber.old.js

export function createHostRootFiber(
  tag: RootTag,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean
): Fiber {
  // 声明一个变量mode
  let mode;
  // 如果tag是ConcurrentRoot
  if (tag === ConcurrentRoot) {
    ///...
  } else {
    // 将mode设置为NoMode
    mode = NoMode;
  }

  // 如果enableProfilerTimer为true且isDevToolsPresent为true
  if (enableProfilerTimer && isDevToolsPresent) {
    // 总是收集 profile timings when DevTools are present.
    // This enables DevTools to start capturing timing at any point–
    // Without some nodes in the tree having empty base times.
    // 将mode设置为ProfileMode
    mode |= ProfileMode;
  }

  // 返回一个Fiber,其类型为HostRoot,生命周期为null,渲染函数为null,模式为mode
  //主要是对mode做了些判断
  //export const HostRoot = 3; 
  return createFiber(HostRoot, null, null, mode);
}

createHostRootFiber-->createFiber

const createFiber = function (
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode
): Fiber {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  return new FiberNode(tag, pendingProps, key, mode);
};

createHostRootFiber-->createFiber-->FiberNode

src\react\v18\react-reconciler\src\ReactFiber.old.js 可以看到FiberNode作为构造函数 也是进行一些属性的初始化

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode
) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  // Fiber
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  this.alternate = null;

  if (enableProfilerTimer) {
    // Note: The following is done to avoid a v8 performance cliff.
    //
    // Initializing the fields below to smis and later updating them with
    // double values will cause Fibers to end up having separate shapes.
    // This behavior/bug has something to do with Object.preventExtension().
    // Fortunately this only impacts DEV builds.
    // Unfortunately it makes React unusably slow for some applications.
    // To work around this, initialize the fields below with doubles.
    //
    // Learn more about this here:
    // https://github.com/facebook/react/issues/14365
    // https://bugs.chromium.org/p/v8/issues/detail?id=8538
    this.actualDuration = Number.NaN;
    this.actualStartTime = Number.NaN;
    this.selfBaseDuration = Number.NaN;
    this.treeBaseDuration = Number.NaN;

    // It's okay to replace the initial doubles with smis after initialization.
    // This won't trigger the performance cliff mentioned above,
    // and it simplifies other profiler code (including DevTools).
    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
  }

  if (__DEV__) {
    // This isn't directly used but is handy for debugging internals:

    this._debugSource = null;
    this._debugOwner = null;
    this._debugNeedsRemount = false;
    this._debugHookTypes = null;
    if (!hasBadMapPolyfill && typeof Object.preventExtensions === "function") {
      Object.preventExtensions(this);
    }
  }
}

此时 代码回到createFiberRoot中的createHostRootFiber

const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride
  );
root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

此时root(也就是FiberRootNode).current指向了FiberNode

FiberNode.stateNode指向了FiberRootNode

createFiberRoot-->retainCache

设置了缓存 src\react\v18\react-reconciler\src\ReactFiberCacheComponent.old.js

createFiberRoot-->initializeUpdateQueue

src\react\v18\react-reconciler\src\ReactUpdateQueue.old.js

export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
      interleaved: null,
      lanes: NoLanes,
    },
    effects: null,
  };
  //此时的fiber是fiberNode
  fiber.updateQueue = queue;
}

这段代码是用于初始化一个更新队列的函数。在 React 中,为了跟踪组件的更新,需要有一个更新队列(UpdateQueue)来存储相关的更新状态。这个函数的作用是创建一个更新队列,并将其绑定到传入的 Fiber 对象上。

实现原理如下:

  1. 创建一个名为 queue 的对象,该对象包含以下属性:

    • baseState:当前组件的状态。
    • firstBaseUpdate:第一个基础更新。
    • lastBaseUpdate:最后一个基础更新。
    • shared:共享状态,包含 pending、interleaved 和 lanes 属性。
    • effects:效果对象。
  2. 将更新队列 queue 绑定到传入的 Fiber 对象上。

用途:在 React 中,当一个组件发生更新时,需要创建一个新的更新队列来存储相关的更新状态。这个函数就是用于初始化这个更新队列。

注意事项:在实际应用中,Fiber 对象通常是在组件的生命周期钩子中创建的,而不是在组件内部。因此,通常情况下,不需要手动调用这个函数来初始化更新队列。React 会自动处理这个过程。

createFiberRoot-->return root

最后createFiberRoot返回了创建的root:

//...
const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError
  ): any);
  //...
  return root;
  //类型 是 FiberRootNode

createRoot-->createContainer执行完毕

返回了root

const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );

createRoot-->markContainerAsRoot

先看下是如何调用的:

markContainerAsRoot(root.current, container);

其中:container是div#root,root.current是fiberNode

src\react\v18\react-dom\src\client\ReactDOMComponentTree.js

const randomKey = Math.random()
  .toString(36)
  .slice(2);
const internalContainerInstanceKey = '__reactContainer$' + randomKey;
export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
  node[internalContainerInstanceKey] = hostRoot;
}

所以上面的代码就是将container添加属性internalContainerInstanceKey,值就是root.current;

createRoot-->listenToAllSupportedEvents

src\react\v18\react-dom\src\events\DOMPluginEventSystem.js

export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
//通过给div#app添加listeningMarker防止事件重复监听;
  if (!(rootContainerElement: any)[listeningMarker]) {
    (rootContainerElement: any)[listeningMarker] = true;
    allNativeEvents.forEach(domEventName => {
      // We handle selectionchange separately because it
      // doesn't bubble and needs to be on the document.
      if (domEventName !== 'selectionchange') {
        if (!nonDelegatedEvents.has(domEventName)) {
          listenToNativeEvent(domEventName, false, rootContainerElement);
        }
        listenToNativeEvent(domEventName, true, rootContainerElement);
      }
    });
    const ownerDocument =
      (rootContainerElement: any).nodeType === DOCUMENT_NODE
        ? rootContainerElement
        : (rootContainerElement: any).ownerDocument;
    if (ownerDocument !== null) {
      // The selectionchange event also needs deduplication
      // but it is attached to the document.
      if (!(ownerDocument: any)[listeningMarker]) {
        (ownerDocument: any)[listeningMarker] = true;
        listenToNativeEvent('selectionchange', false, ownerDocument);
      }
    }
  }
}

主要是对事件做一些绑定,selectionchange需要特殊处理,需要绑定到document上面.

在下一个系列中详细阐述事件监听机制. 我们先来看ReactDOMRoot

creatRoot-->ReactDOMRoot

如何调用:

return new ReactDOMRoot(root);
function ReactDOMRoot(internalRoot: FiberRoot) {
  this._internalRoot = internalRoot;
}

ReactDOMRoot作为构造函数,就是在实例上添加了_internalRoot指向了传入的root(FiberRoot)

总结

ReactDOM.createRoot(root).render();在createRoot当中具体做了写什么:

  1. 调用了createRoot方法,在这个方法中做了些判定:是否是合法的容器,对不合法的容器做了一些警告提示;判断根容器是不是已经被标记为根节点;
  2. 在createRoot中,又调用了createContainer(返回createFiberRoot),其实就是创建fiberRoot;
  3. 创建fiberRoot和FiberNode,并用root.current = uninitializedFiber; uninitializedFiber.stateNode = root;互相指向;
  4. 调用initializeUpdateQueue 对新建的fiberNode添加updateQueue更新队列
  5. 返回创建的fiberRoot
  6. 通过在div#root上添加属性值为fibernode方式标记为根节点
  7. 进行事件的监听
  8. 最后添加_internalRoot指向FiberNode并返回实例