React是如何启动的

date
Jan 9, 2022
slug
react-qidong
status
Published
tags
React
源码
summary
从源码解析React启动过程
type
Post
本文基于react17.0.0
在贴源码前,先简单介绍下react的三种启动模式:
  1. legacy mode:通过ReactDOM.render(<App/>, root)进入,不支持一些新特性
  1. blocking mode: 通过ReactDOM.createBlokingRoot().render(<App/>,)进入,支持concurrent mode中的部分特性
  1. concurrent mode: 通过ReactDOM.createRoot().render(<App/>)进入,支持所有新特性
tips:
  1. 其中blocking modeconcurrent mode均在实验阶段,在react18中,后者将正式成为一个可选的mode
  1. 虽然createBlockingModecreateRoot存在于17.x的原码中,但ReactDOM并没有将它们暴露出来,也就是说想体验这两个模式得将react安装为体验版

legacy mode

当我们调用ReactDOM.render时,内部会调用legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {

  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  if (!root) {
    // 初次挂载时
    // container指ReactDOM.render的第二个参数(即应用挂载的DOM节点)
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        // instance最终指向 children(入参: 如<App/>)生成的DOM节点
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // 调用更新,第一次更新不需要批量
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    // root已经初始化过的情况
  	// 获取FiberRoot对象
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // 调用更新
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}
在首次挂载时,会调用legacyCreateRootFromDOMContainer,该方法内部会调用createLegacyRoot,它内部会执行new ReactDOMBlockingRoot并返回
function legacyCreateRootFromDOMContainer(
  container: Container,
  forceHydrate: boolean,
): RootType {

  // ...处理hydrate

  return createLegacyRoot(
    container,
    shouldHydrate
      ? {
          hydrate: true,
        }
      : undefined,
  );
}

export function createLegacyRoot(
  container: Container,
  options?: RootOptions,
): RootType {
  return new ReactDOMBlockingRoot(container, LegacyRoot, options); // 注意这里的LegacyRoot是固定的, 并不是外界传入的
}
ReactDOMBlockingRoot中,会通过调用createRootImpl,创建一个fiberRoot对象,并挂载到this._internalRoot上;此外,会在ReactDOMBlockingRoot的原型上挂载renderunmount方法
function ReactDOMBlockingRoot(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {

  this._internalRoot = createRootImpl(container, tag, options);
}

ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
  children: ReactNodeList,
): void {

  const root = this._internalRoot;
  // 调用更新
  updateContainer(children, root, null, null);
};

ReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount = function(): void {

  const root = this._internalRoot;
  const container = root.containerInfo;
  // 调用更新
  updateContainer(null, root, null, () => {
    unmarkContainerAsRoot(container);
  });
};
继续看createRootImpl,内部主要做了三件事:
  1. 通过createContainer创建fiberRoot
  1. 标记DOM,将DOM和fiber对象关联起来
  1. 在该节点上进行事件监听
function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {

  // ...hydrate部分

  // 创建fiberRoot
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);

  // 标记DOM,把DOM和fiber对象关联起来
  markContainerAsRoot(root.current, container);

  // COMMENT_NODE = 8,代表注释,nodeType = 1, 代表元素
  // nodeType详细可看https://www.w3school.com.cn/jsref/prop_node_nodetype.asp
  const rootContainerElement =
    container.nodeType === COMMENT_NODE ? container.parentNode : container;

  // 在该节点上进行事件监听
  listenToAllSupportedEvents(rootContainerElement);

  // 省略不相干代码

  return root;
}
createContainer的内部调用了createFiberRoot来创建fiberRoot
export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
  // 创建fiberRoot对象
  return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks); // tag在三种mode下不同:ConcurrentRoot,BlockingRoot,LegacyRoot
}
createFiberRoot内部,创建了react中的第一个fiber对象:HostRootFiber
export function createFiberRoot(
  // containerInfo就是根节点,如<div id='app'></div>
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {

  // 创建fiberRootNode
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
	// 创建了react中的第一个fiber对象
  const uninitializedFiber = createHostRootFiber(tag);
  // root.current指向rootFiber,root.current指向哪棵Fiber树,页面上就显示该Fiber树对应的dom
  root.current = uninitializedFiber;
  // rootFiber.stateNode指向FiberRoot,可通过stateNode.containerInfo取到对应的dom根节点div#root
  uninitializedFiber.stateNode = root;
  // 初始化updateQueue,对于RootFiber,queue.shared.pending上面存储着React.element
  initializeUpdateQueue(uninitializedFiber);

  return root;
}
在创建HostRootFiber时,mode的值会关联传入的tag(ConcurrentRoot, BlockingRoot, LegacyRoot),而在后续的每个fiber节点创建时,fiber.mode属性都会来自于父节点
export function createHostRootFiber(tag: RootTag): Fiber {

  let mode;
  if (tag === ConcurrentRoot) {
    mode = ConcurrentMode | BlockingMode | StrictMode;
  } else if (tag === BlockingRoot) {
    mode = BlockingMode | StrictMode;
  } else {
    mode = NoMode;
  }

  // ...

  const newFiber = createFiber(HostRoot, null, null, mode);

  return newFiber
}
createFiber内部,则会创建一个FiberNode
const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {

  const fiberNode = new FiberNode(tag, pendingProps, key, mode);

  return fiberNode
};
至此,react的初始化工作就做完了,接下来会调用updateContainer进入首次更新,legacy mode下,会将updateContainer传入unbatchedUpdates中,它的作用是将执行上下文修改为LegacyUnbatchedContext
unbatchedUpdates(() => {
  updateContainer(children, fiberRoot, parentComponent, callback);
});
updateContainer内部(开始更新,进入了reconciler的职责):
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {

  const current = container.current;
  // 获取事件开始时间,一般是performance.now()
  const eventTime = requestEventTime();
  // 获取本次更新优先级
  const lane = requestUpdateLane(current);

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  // 创建更新任务
  const update = createUpdate(eventTime, lane);
  // 对于container,其update的payload就是React.element
  update.payload = { element };
  // 对于ReactDOM.createRoot(Concurrent模式)的render来说,callback为null
  // 对于ReactDOM.render来说,callback为ReactDOM.render的第三个参数
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }
  // 往updateQueue加入update
  enqueueUpdate(current, update);
  // 调度更新
  scheduleUpdateOnFiber(current, lane, eventTime);

  return lane;
}
总之,在ReactDOM.render后,会进行如下操作:
  1. 对react的初始化
  1. 创建ReactBlockingRoot对象,挂载到this._internalRoot上,该对象有renderunmount方法
  1. 创建fiberRoot对象,作为reconciler运行时的全局上下文,保存fiber构建时需要的状态
  1. 创建HostRootFiber对象,fiber tree的根节点,类型为HostRoot
  1. 调度更新,进入reconciler的工作职责,本文不深入

© kaba 2019 - 2023