深入了解React的渲染流程
Howard 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 阶段的计算是可以打断的,让更高优先级的更新去优先计算。