深入了解React的渲染流程

11/24/2023 React

在 React 18 中,最大的变化是 React 默认开启并发模式,这个其实在 16.8 版本之后,React 就已经开始铺垫,到了 18 版本之后,终于全面开启。

有了并发模式,对于大而复杂项目来说,网页与用户的交互变得更流程,响应更快。React 把每次更新(render, setState, forceUpdate)将赋予优先级,并更新使用调度器,让其来调度所有更新的顺序。

对于 React 的核心渲染流程中,变化不算太大,更以前的版本有微小的改动,总体的核心流程没有太大的变化。这篇文章主要就是讲解 React 的渲染流程。

# React 的虚拟节点-Fiber

虚拟 DOM 是前端框架最重要的技术点,通过 js 对象来纪录每个节点的信息,也是贯穿整个 React 框架和深入了解 React 框架最重要的一点。每个虚拟节点不一定对应真实的 DOM,但真实的 DOM 一定是有一个虚拟节点 Fiber。这是虚拟节点的属性:

function FiberNode(
  this: $FlowFixMe,
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode
) {
  // 节点的基础信息
  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.refCleanup = null;

  // Fiber的props属性和state相关属性
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // Fiber的纪录的一些变化,例如被删除,更新,插入等
  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);
    }
  }
}

# React 的渲染

React 的渲染主要分为两个阶段,render 以及 commit。

这两个阶段中,只有 render 阶段是分片执行,意味着 render 阶段的计算是可以打断的,让更高优先级的更新去优先计算。

深度解析 React-render 阶段:递与归

React Commit 阶段解析