阅读 195

React 源码解读之常用变量

React 源码中有很多常用变量,如标记节点类型的 fiber.tag,标记更新类型的fiber.flags,标记启动模式的fiber.mode 等等。

WorkTag 元素类型

WorkTag 用来标记 React 中的不同元素,如原生HTML标签元素、Function组件、class组件、Provider组件、Consumer组件、Fragment组件等等。这些通常体现在 fiber 的 tag 值上。

// packages/react-reconciler/src/ReactInternalTypes.js

export type Fiber = {|

  // Tag identifying the type of fiber.
  tag: WorkTag,
                     
  // 删除了其它属性定义 

|};复制代码

具体的 WorkTag 如下:

// packages/react-reconciler/src/ReactWorkTags.js

export type WorkTag =
  | 0
  | 1
  | 2
  | 3
  | 4
  | 5
  | 6
  | 7
  | 8
  | 9
  | 10
  | 11
  | 12
  | 13
  | 14
  | 15
  | 16
  | 17
  | 18
  | 19
  | 20
  | 21
  | 22
  | 23
  | 24;

export const FunctionComponent = 0; // 函数组件
export const ClassComponent = 1; // class 组件
// 函数组件 或 class 组件
export const IndeterminateComponent = 2; // Before we know whether it is function or class
// 根节点,可能嵌套
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
// 传送门组件
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5; // 原生标签组件
export const HostText = 6; // 文本
export const Fragment = 7; // Fragment 组件
export const Mode = 8;     // 模式类型
export const ContextConsumer = 9; // Context Consumer 组件
export const ContextProvider = 10; // Context Provider 组件
export const ForwardRef = 11; // ForwardRef 组件,转发 ref 属性到子组件
export const Profiler = 12;  // Profiler 组件
export const SuspenseComponent = 13; // 懒加载组件
export const MemoComponent = 14; // React.memo 组件,通过检查 props 变更决定是否重新渲染组件
export const SimpleMemoComponent = 15;
export const LazyComponent = 16; // 动态加载组件
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;复制代码

React Fiber 会从 packages/react-reconciler/src/ReactFiberBeginWork.new.js 文件中的 beginWork() 开始执行,在beginWork() 中,会根据不同的WorkTag来执行不同的处理。

flags 副作用

React 16.8.x 版本中副作用使用 SideEffectTag 标记,体现在 fiber.effectTag 属性值上。React 17.0.x 版本中的副作用使用 Flags 标记,体现在 fiber.flags 属性值上。

Flags 用来标记 React 中更新的类型,如没有更新是 NoFlags,插入为 Placement,更新为 Update 等等。具体的 Flags 如下:

// packages/react-reconciler/src/ReactFiberFlags.js

export type Flags = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoFlags = /*                      */ 0b00000000000000000000000;
export const PerformedWork = /*                */ 0b00000000000000000000001;

// You can change the rest (and add more).
export const Placement = /*                    */ 0b00000000000000000000010;
export const Update = /*                       */ 0b00000000000000000000100;
export const PlacementAndUpdate = /*           */ Placement | Update;
export const Deletion = /*                     */ 0b00000000000000000001000;
export const ChildDeletion = /*                */ 0b00000000000000000010000;
export const ContentReset = /*                 */ 0b00000000000000000100000;
export const Callback = /*                     */ 0b00000000000000001000000;
export const DidCapture = /*                   */ 0b00000000000000010000000;
export const Ref = /*                          */ 0b00000000000000100000000;
export const Snapshot = /*                     */ 0b00000000000001000000000;
export const Passive = /*                      */ 0b00000000000010000000000;
export const Hydrating = /*                    */ 0b00000000000100000000000;
export const HydratingAndUpdate = /*           */ Hydrating | Update;
export const Visibility = /*                   */ 0b00000000001000000000000;

export const LifecycleEffectMask = Passive | Update | Callback | Ref | Snapshot;

// Union of all commit flags (flags with the lifetime of a particular commit)
export const HostEffectMask = /*               */ 0b00000000001111111111111;

// These are not really side effects, but we still reuse this field.
export const Incomplete = /*                   */ 0b00000000010000000000000;
export const ShouldCapture = /*                */ 0b00000000100000000000000;
export const ForceUpdateForLegacySuspense = /* */ 0b00000001000000000000000;
export const DidPropagateContext = /*          */ 0b00000010000000000000000;
export const NeedsPropagation = /*             */ 0b00000100000000000000000;

// Static tags describe aspects of a fiber that are not specific to a render,
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
// This enables us to defer more work in the unmount case,
// since we can defer traversing the tree during layout to look for Passive effects,
// and instead rely on the static flag as a signal that there may be cleanup work.
export const RefStatic = /*                    */ 0b00001000000000000000000;
export const LayoutStatic = /*                 */ 0b00010000000000000000000;
export const PassiveStatic = /*                */ 0b00100000000000000000000;

// These flags allow us to traverse to fibers that have effects on mount
// without traversing the entire tree after every commit for
// double invoking
export const MountLayoutDev = /*               */ 0b01000000000000000000000;
export const MountPassiveDev = /*              */ 0b10000000000000000000000;

// Groups of flags that are used in the commit phase to skip over trees that
// don't contain effects, by checking subtreeFlags.

export const BeforeMutationMask =
  // TODO: Remove Update flag from before mutation phase by re-landing Visiblity
  // flag logic (see #20043)
  Update |
  Snapshot |
  (enableCreateEventHandleAPI
    ? // createEventHandle needs to visit deleted and hidden trees to
      // fire beforeblur
      // TODO: Only need to visit Deletions during BeforeMutation phase if an
      // element is focused.
      ChildDeletion | Visibility
    : 0);

export const MutationMask =
  Placement |
  Update |
  ChildDeletion |
  ContentReset |
  Ref |
  Hydrating |
  Visibility;
export const LayoutMask = Update | Callback | Ref | Visibility;

// TODO: Split into PassiveMountMask and PassiveUnmountMask
export const PassiveMask = Passive | ChildDeletion;

// Union of tags that don't get reset on clones.
// This allows certain concepts to persist without recalculting them,
// e.g. whether a subtree contains passive effects or portals.
export const StaticMask = LayoutStatic | PassiveStatic | RefStatic;复制代码

这里的 flags 都是二进制,这个和 React 中用到的位运算有关。位运算只能用于整数,并且是直接对二进制位进行计算,直接处理每一个比特位,是非常底层的运算,运算速度极快。

例如 workInProgress.flags |= Placement; 这里就是给 workInProgress 添加一个 Placement 的副作用。又比如 finishedWork.flags &= ~Placement; 这里则是给 finishedWork 清除一个 Placement 的副作用。

这种处理不仅速度快,而且简洁方便,是非常巧妙的方式,值得我们学习借鉴。

ExecutionContext 执行栈环境

ExecutionContext 用来标记 React 执行栈 (React execution stack) 中目前所处的环境,所对应的全局变量是 packages/react-reconciler/src/ReactFiberWorkLoop.new.js 文件中的 executionContext。

// Describes where we are in the React execution stack
let executionContext: ExecutionContext = NoContext;复制代码

ExecutionContext 同样也用到了位运算,具体的 ExecutionContext 变量如下:

// packages/react-reconciler/src/ReactFiberWorkLoop.new.js

type ExecutionContext = number;

export const NoContext = /*             */ 0b0000;
const BatchedContext = /*               */ 0b0001;
const RenderContext = /*                */ 0b0010;
const CommitContext = /*                */ 0b0100;
export const RetryAfterError = /*       */ 0b1000;


 executionContext |= BatchedContext;
executionContext |= RenderContext;复制代码

例如 executionContext |= BatchedContext; 这里是给 React 执行栈添加一个 BatchedContext 的环境。又比如 executionContext |= RenderContext; 这里则是给 React 执行长添加一个 RenderContext 的环境。

RootExitStatus 根节点退出状态

RootExitStatus 用来标记根节点退出时的状态,对应的全局变量是 packages/react-reconciler/src/ReactFiberWorkLoop.new.js 文件中的 workInProgressRootExitStatus。

// Whether to root completed, errored, suspended, etc.
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;复制代码

其中 RootIncomplete 表示为未完成,RootCompleted 表示为已完成。具体的 RootExitStatus 变量如下:

type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5;
const RootIncomplete = 0; // 未完成
const RootFatalErrored = 1;
const RootErrored = 2; 
const RootSuspended = 3;
const RootSuspendedWithDelay = 4;
const RootCompleted = 5; // 已完成复制代码

RootTag 启动模式类型

RootTag 用来标记React app 启动的模式类型,目前默认是 LegacyRoot 模式,ConcurrentRoot 模式在 v18.0.0-alpha 和experiment 版本中发布。 这个模式开启了所有的新功能。

// packages/react-reconciler/src/ReactRootTags.js

export type RootTag = 0 | 1;

export const LegacyRoot = 0;
export const ConcurrentRoot = 1;复制代码
  1. legacy 模式: ReactDOM.render(<App />, rootNode)。这是当前 React app 使用的方式,当前没有计划删除该模式。这个模式可能不支持这些新功能(concurrent 支持的所有功能)。

  2. Concurrent 模式: ReactDOM.createRoot(rootNode).render(<App />)。打算作为 React 的默认开发模式。目前在 v18.0.0-alpha 和 experiment 版本中发布。这个模式开启了所有的新功能。

PriorityLevel 更新优先级

PriorityLevel 用来标记更新的优先级,如提交更新的用 ImmediatePriority ,即立即执行的优先级。而用户交互的行为事件的优先级是 UserBlockingPriority。PriorityLevel 的数值越大,代表优先级越低。

// packages/scheduler/src/SchedulerPriorities.js

export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;

// TODO: Use symbols?
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2; // 用户的交互行为
export const NormalPriority = 3;
export const LowPriority = 4; // 低优先级,如异步
export const IdlePriority = 5;复制代码

currentEventTime

// packages/react-reconciler/src/ReactFiberWorkLoop.new.js

let currentEventTime: number = NoTimestamp;复制代码

过期时间是根据当前时间计算出来的 (当前时间就是指开始时间)。在 React 中,如果两个 update 是在同一个事件上进行调度的,那么应该把它们的开始时间看作是同步的,实际上两个 update 的开始时间是有差值的,但是可以忽略不计。

换句话说,由于是过期时间决定了 update 是如何批量执行的,我们希望相似优先级并且发生在同一个事件上的 update 接收相同的过期时间。

currentEventTime 的获取是通过 requestEventTime() 函数获取的,其代码如下:

// packages/react-reconciler/src/ReactFiberWorkLoop.new.js
export function requestEventTime() {
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    // We're inside React, so it's fine to read the actual time.
    return now();
  }
  // We're not inside React, so we may be in the middle of a browser event.
  if (currentEventTime !== NoTimestamp) {
    // Use the same start time for all updates until we enter React again.
    return currentEventTime;
  }
  // This is the first update since React yielded. Compute a new start time.
  currentEventTime = now();
  return currentEventTime;
}复制代码

总结

本文介绍了React源码中的一些常用变量,提前了解这些变量,对于后续阅读源码是非常有用的。


作者:紫圣
链接:https://juejin.cn/post/7020178232062246949


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐