Bitwise operations in ReactJS

Intermediate
Last updated October 24, 2024

Exploring how ReactJS uses binary masks with binary operations under the hood.

Event Type Usage

React's internal implementation makes extensive use of bitwise operations and bitmasks for efficient state management and feature flagging. This post explores how and why React employs these low-level operations in its core functionality.

TL;DR#

React uses bitmasks extensively throughout its codebase, primarily because they enable fast and efficient checking of state inclusion and feature flags. Bitwise operations allow React to pack multiple boolean flags into a single integer, reducing memory usage and improving performance.

Disclaimer#

In this post I assume you're already familiar with bitwise operations, so I won't explain the basics.


Deep dive into react-reconciler#

React Fiber flags

The first notable example is the usage of bitmasks to define node types in React Fiber Tree. Within the Fiber implementation, node states like Incomplete, NeedsPropagation, and others are defined using bitmasks.

packages/react-reconciler/src/ReactFiberFlags.js

export const Update = /*          */ 0b0000000000000000000000000100;
export const Cloned = /*          */ 0b0000000000000000000000001000;
 
export const ChildDeletion = /*   */ 0b0000000000000000000000010000;
export const ContentReset = /*    */ 0b0000000000000000000000100000;
 
...
 
export const Incomplete = /*      */ 0b0000000000001000000000000000;
export const ShouldCapture = /*   */ 0b0000000000010000000000000000;
export const DidPropagateContext =   0b0000000001000000000000000000;
export const NeedsPropagation = /**/ 0b0000000010000000000000000000;
 
 
export const BeforeMutationMask: number =
  Update |
  Snapshot |
  (enableCreateEventHandleAPI ? ChildDeletion | Visibility : 0);

These flags are then used to determine whether a subtree needs rerendering. Here's a key example from the reconciliation process:

packages/react-reconciler/src/ReactFiberWorkLoop.js

...
const rootHasEffect =
  (finishedWork.flags &
    (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
  NoFlags;

Let's break down how this code works:

  1. First, it combines multiple masks using the OR(|) operator: BeforeMutationMask | MutationMask | LayoutMask | PassiveMask
  2. Then, it performs a bitwise AND (&) between finishedWork.flags and the combined masks
  3. Finally, it checks if the result is not equal to NoFlags (which is zero or 0b0)
  4. If the node flags have no common flags with our search masks, the check evaluates to 0 !== 0, returning false

React Execution Modes

React also uses bitmasks to specify its execution modes:

packages/react-reconciler/src/ReactTypeOfMode.js

export type TypeOfMode = number;
 
export const NoMode = /*                         */ 0b0000000;
export const ConcurrentMode = /*                 */ 0b0000001;
export const ProfileMode = /*                    */ 0b0000010;
export const DebugTracingMode = /*               */ 0b0000100;
export const StrictLegacyMode = /*               */ 0b0001000;
export const StrictEffectsMode = /*              */ 0b0010000;
export const NoStrictPassiveEffectsMode = /*     */ 0b1000000;

React internally supports seven distinct modes:

  • NoMode: This mode signifies the default setting where no specific mode is applied to React operations.
  • ConcurrentMode: Enables concurrent features, allowing React to interrupt rendering work to prioritize more urgent tasks.
  • ProfileMode: Activates additional profiling and diagnostic information to help with performance optimization.
  • DebugTracingMode: Facilitates debugging by tracing component renders and their causes, useful for development environments.
  • StrictLegacyMode: Emulates certain behaviors from older versions of React, helping gradually adapt to newer changes.
  • StrictEffectsMode: Enhances effect handling for more predictable and safer side-effects in React components.
  • NoStrictPassiveEffectsMode: A specific mode that relaxes the rules around passive effects, offering compatibility or performance adjustments.

To check if a fiber node is in debug mode, React performs a bitwise AND operation:

packages/react-reconciler/src/ReactFiberThrow.js

if (__DEV__) {
  if (enableDebugTracing) {
    if (sourceFiber.mode & DebugTracingMode) {
      const name = getComponentNameFromFiber(sourceFiber) || "Unknown";
      logComponentSuspended(name, wakeable);
    }
  }
}

One elegant aspect is how easily React combines fiber node modes using the bitwise OR operator:

packages/react-reconciler/src/ReactFiber.js

case REACT_DEBUG_TRACING_MODE_TYPE:
  if (enableDebugTracing) {
    fiberTag = Mode;
    mode |= DebugTracingMode;
    break;
  }

Event Types

React's event system also leverages bitmasks for flag management:

packages/react-dom-bindings/src/events/EventSystemFlags.js

export type EventSystemFlags = number;
 
export const IS_EVENT_HANDLE_NON_MANAGED_NODE = 1;
export const IS_NON_DELEGATED = 1 << 1;
export const IS_CAPTURE_PHASE = 1 << 2;
export const IS_PASSIVE = 1 << 3;
export const IS_LEGACY_FB_SUPPORT_MODE = 1 << 4;
 
export const SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE =
  IS_LEGACY_FB_SUPPORT_MODE | IS_CAPTURE_PHASE;
 
export const SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS =
  IS_EVENT_HANDLE_NON_MANAGED_NODE | IS_NON_DELEGATED | IS_CAPTURE_PHASE;

These flags are used in two primary ways:

  1. Adding modes using the OR operation
  2. Checking for specific modes using the AND operation:
packages/react-dom-bindings/src/events/DOMPluginEventSystem.js

const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;

Interesting observations:

  • Event types use left shift notation (1 << 2) instead of explicit binary notation (0b0000000100000000000000000000)
  • The IS_PASSIVE event type exists in the codebase but is currently unused in the React monorepo

Notable Exception: Scheduler Priorities#

Interestingly, React's scheduler priorities don't use bitmasks:

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;

This design choice makes sense because priorities require comparison operations (greater than, less than) rather than set inclusion checks. While it would be possible to implement priorities using bitmasks, simple numeric values are more appropriate for this use case.

I intentionally left TODO in the code. It's original TODO from React codebase. Apparently, they are thinking on changing proprieties to Symbol primitives, although plain integers seems elegant and convenient.


Summary#

React's use of bitwise operations demonstrates thoughtful performance optimization. Bitmasks are employed when checking for feature inclusion or state combinations, while simpler numeric values are used for comparative operations like priority scheduling. This selective use of bitwise operations shows how React's internal architecture carefully chooses the right tool for each specific requirement.

Launch Alert!

Don't miss out - subscribe to be notified when the full cheatsheet is available 🚀