Bitwise operations in ReactJS
Exploring how ReactJS uses binary masks with binary operations under the hood.
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.
These flags are then used to determine whether a subtree needs rerendering. Here's a key example from the reconciliation process:
Let's break down how this code works:
- First, it combines multiple masks using the
OR
(|
) operator:BeforeMutationMask | MutationMask | LayoutMask | PassiveMask
- Then, it performs a bitwise
AND
(&
) betweenfinishedWork.flags
and the combined masks - Finally, it checks if the result is not equal to
NoFlags
(which is zero or0b0
) - If the node flags have no common flags with our search masks, the check evaluates to
0 !== 0
, returningfalse
React Execution Modes
React also uses bitmasks to specify its execution modes:
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:
One elegant aspect is how easily React combines fiber node modes using the bitwise OR
operator:
Event Types
React's event system also leverages bitmasks for flag management:
These flags are used in two primary ways:
- Adding modes using the
OR
operation - Checking for specific modes using the
AND
operation:
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:
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.