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()方法的内部执行过程。

相关推荐
aha-凯心14 分钟前
前端学习 vben 之 axios interceptors
前端·学习
熊出没31 分钟前
Vue前端导出页面为PDF文件
前端·vue.js·pdf
VOLUN31 分钟前
Vue3项目中优雅封装API基础接口:getBaseApi设计解析
前端·vue.js·api
此乃大忽悠37 分钟前
XSS(ctfshow)
javascript·web安全·xss·ctfshow
用户99045017780091 小时前
告别广告干扰,体验极简 JSON 格式化——这款工具让你专注代码本身
前端
前端极客探险家1 小时前
告别卡顿与慢响应!现代 Web 应用性能优化:从前端渲染到后端算法的全面提速指南
前端·算法·性能优化
袁煦丞2 小时前
【局域网秒传神器】LocalSend:cpolar内网穿透实验室第418个成功挑战
前端·程序员·远程工作
江城开朗的豌豆2 小时前
Vuex数据突然消失?六招教你轻松找回来!
前端·javascript·vue.js
好奇心笔记2 小时前
ai写代码随机拉大的,所以我准备给AI出一个设计规范
前端·javascript
江城开朗的豌豆2 小时前
Vue状态管理进阶:数据到底是怎么"跑"的?
前端·javascript·vue.js