React19为什么一定要使用位运算?

5 阅读3分钟

一、位运算基础

1.1 二进制本质

JS 的 Number 在做位运算时:会转为 32 位有符号整数。

例如:

1 = 00000001
2 = 00000010
4 = 00000100
8 = 00001000

1.2 核心运算

(1)按位或 | —— 合并状态

1 | 2 = 3
0001
0010
----
0011

工程含义:合并多个状态

(2)按位与 & —— 判断包含

flags = 1 | 4 // 0101

flags & 4 !== 0  // true

工程含义:判断某状态是否存在

(3)按位非 ~ —— 取反

~0001 = 1110

工程含义:用于“移除某状态”

(4)组合删除

flags = flags & ~Placement

工程含义:从集合中删除某个状态

1.3 为什么它像“集合”?

因为:每一位就是一个独立布尔位

它等价于:一个最多 31 个元素的集合

二、位运算在项目中的应用场景

2.1 权限系统

const READ  = 1 << 0
const WRITE = 1 << 1
const EXEC  = 1 << 2

组合:

const userPermission = READ | WRITE

判断:

if (userPermission & WRITE) {
  // 有写权限
}

优点:

  • 一个 number 存储所有权限
  • O(1) 判断,如果是数组或者对象什么的需要 O(n)
  • 内存占用极小

2.2 游戏引擎中的状态管理

例如角色状态:

受伤 | 无敌 | 中毒 | 隐身

这些都可以 bitmask 表示。

原因:

  • 高频判断
  • 状态组合多
  • 内存要求高

2.3 调度系统中的优先级集合

例如:

任务 A 优先级 1
任务 B 优先级 4
任务 C 优先级 8

合并:

pending = 1 | 4 | 8

选择最高优先级:

lowestBit = pending & -pending

这个技巧 React 里大量使用。

三、React19 中位运算的核心使用场景

3.1 ReactFiberFlags —— 副作用标记系统

源码位置:

packages/react-reconciler/src/ReactFiberFlags.js
export const Placement = 0b00000000000000010;
export const Update    = 0b00000000000000100;
export const Deletion  = 0b00000000000001000;
export const Passive   = 0b00000000001000000;

一个 Fiber 可能:

  • 既要插入 DOM
  • 又要更新属性
  • 还要执行 effect

所以:

fiber.flags = Placement | Update | Passive

判断:

if (fiber.flags & Placement)

这时候有同学就要问了~ 为什么不使用数组呢?假设使用数组:

fiber.flags = ['Placement', 'Update']

问题:

  • includes 是 O(n)
  • 每个 Fiber 都有数组
  • GC 压力大
  • commit 阶段是高频遍历

React commit 深度遍历整棵 Fiber 树,会产生很大的性能问题。

位运算是 CPU 指令级别。

3.2 subtreeFlags —— 子树聚合优化

React 19 里:

fiber.subtreeFlags

它表示:当前 Fiber 子树是否包含某些副作用

用途:

在 commit 阶段:

if ((fiber.subtreeFlags & PassiveMask) !== NoFlags)

可以一次判断整棵子树,而不必递归。

如果用数组,无法做到:O(1) 子树状态判断。

3.3 ReactFiberLane —— 并发优先级系统

这是位运算最核心的地方。

源码:

ReactFiberLane.js

例如:

export const SyncLane              = 0b0000000000000000001;
export const InputContinuousLane   = 0b0000000000000000010;
export const DefaultLane           = 0b0000000000000000100;

一个 root 可能同时有多个 pending 更新:

root.pendingLanes = laneA | laneB | laneC

选择最高优先级任务

function getHighestPriorityLane(lanes) {
  return lanes & -lanes;
}

这是经典位运算技巧:

x & -x = 最低位 1

作用:O(1) 选出最高优先级 lane

如果用数组:

  • 需要排序
  • 需要遍历
  • 需要比较