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;复制代码
legacy 模式:
ReactDOM.render(<App />, rootNode)
。这是当前 React app 使用的方式,当前没有计划删除该模式。这个模式可能不支持这些新功能(concurrent 支持的所有功能)。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