react 版本:v17.0.3
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; 这里是给 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;
EventPriority 事件优先级
EventPriority 用来标记事件的优先级,事件优先级更新优先级的根源。
// packages/react-reconciler/src/ReactEventPriorities.new.js
export opaque type EventPriority = Lane;
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;
在React事件体系中,主要有三种事件优先级:
-
**DiscreteEventPriority:**离散事件,如 click、keydown、focusin 等,这些事件的触发不是连续的,优先级最高
-
**ContinuousEventPriority:**阻塞事件,如 drag、mousemove、scroll 等,这些事件的特点是连续触发,会阻塞渲染,优先级为适中
-
**DefaultEventPriority:**如 load、animation 等事件,优先级最低
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源码中的一些常用变量,提前了解这些变量,对于后续阅读源码是非常有用的。