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

相关推荐
Z_ One Dream11 分钟前
css 在 hover 子元素时,不要让父元素触发 hover 效果
前端·javascript·css
码农幻想梦5 小时前
实验九 视图的使用
前端·数据库·oracle
开心工作室_kaic7 小时前
ssm010基于ssm的新能源汽车在线租赁管理系统(论文+源码)_kaic
java·前端·spring boot·后端·汽车
Python私教7 小时前
Flutter颜色和主题
开发语言·javascript·flutter
大力水手~8 小时前
css之loading旋转加载
前端·javascript·css
Nguhyb8 小时前
-XSS-
前端·xss
前端郭德纲8 小时前
深入浅出ES6 Promise
前端·javascript·es6
就爱敲代码8 小时前
ES6 运算符的扩展
前端·ecmascript·es6
天天进步20159 小时前
Lodash:现代 JavaScript 开发的瑞士军刀
开发语言·javascript·ecmascript
王哲晓9 小时前
第六章 Vue计算属性之computed
前端·javascript·vue.js