React源码解析(三) —— 应用启动过程

这里以React18的 Concurrent 并发模式作为基础

js 复制代码
ReactDOM.createRoot(rootNode).render(<App />)

createRoot被调用时发生了什么?

通过观察如下调用堆栈我们可以看到具体的过程:

执行的顺序从createRoot函数开始:

js 复制代码
export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
 
  // 省略掉一些处理createRoot options的逻辑
  
  const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    transitionCallbacks,
  );
  return new ReactDOMRoot(root);
}

可以看到在createRoot中会调用createContainer函数

js 复制代码
function createContainer(containerInfo, tag, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, transitionCallbacks) {
  var hydrate = false;
  var initialChildren = null;
  return createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError);
}

createContainer函数 中会调用createFiberRoot

js 复制代码
function createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, 
identifierPrefix, onRecoverableError, transitionCallbacks) {
  var root = new FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError);
  var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;
  {
    var _initialState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: null,
      // not enabled yet
      transitions: null,
      pendingSuspenseBoundaries: null
    };
    uninitializedFiber.memoizedState = _initialState;
  }

  initializeUpdateQueue(uninitializedFiber);
  return root;
}

createFiberRoot中会有如下过程

  • 创建一个 FiberRootNode 的实例 FiberRoot作为整个React应用的根节点
  • 调用 createHostRootFiber 创建 一个尚未初始化的Fiber树
  • FiberRoot.current 设置为 HostRootFiber 将 HostRootFiber 的 stateNode 设置为 FiberRoot在二者之间建立联系
  • 使用initializeUpdateQueue初始化更新队列
  • 返回根节点

ps:更新队列是一个内部的数据结构,用于管理组件的状态更新和上下文变化。它负责跟踪组件的更新,并将它们排队以便在合适的时机批量处理。

root.render()

上一步中createRoot创建的root对象prototype上的方法被调用:

js 复制代码
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
  function (children: ReactNodeList): void {
    const root = this._internalRoot;
    updateContainer(children, root, null, null);
  };

进入updateContainer,updateContainer中处理了lane优先级相关信息,调用了updateContainerImpl进行后续处理,updateContainer相当于ReactDOM和reconciler的桥梁。

js 复制代码
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  const current = container.current;
  const lane = requestUpdateLane(current);
  updateContainerImpl(
    current,
    lane,
    element,
    container,
    parentComponent,
    callback,
  );
  return lane;
}

updateContainerImpl:

  • 调用 enqueueUpdate 函数将更新对象到根 Fiber 节点(rootFiber)上,并指定更新的优先级车道(lane
  • 调用 scheduleUpdateOnFiber 函数将更新调度到根 Fiber 节点上,这将触发协调算法(reconciliation)
js 复制代码
function updateContainerImpl(
  rootFiber: Fiber,
  lane: Lane,
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): void {

  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }

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


  const update = createUpdate(lane);
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  
  if (callback !== null) {
    update.callback = callback;
  }

  const root = enqueueUpdate(rootFiber, update, lane);
  
  if (root !== null) {
    startUpdateTimerByLane(lane);
    // 进入reconciler
    scheduleUpdateOnFiber(root, rootFiber, lane);
    entangleTransitions(root, rootFiber, lane);
  }
}

调用之后会进入调度以及协调的过程,相关内容在后续的文章中更新;

总结

本文解释了React应用启动入口ReactDOM.createRootroot.render()方法的内部执行过程。

相关推荐
黑客老陈21 分钟前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安26 分钟前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
暴富的Tdy1 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se1 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
m0_748235611 小时前
web 渗透学习指南——初学者防入狱篇
前端
℘团子এ1 小时前
js和html中,将Excel文件渲染在页面上
javascript·html·excel
z千鑫1 小时前
【前端】入门指南:Vue中使用Node.js进行数据库CRUD操作的详细步骤
前端·vue.js·node.js
m0_748250742 小时前
Web入门常用标签、属性、属性值
前端