Inference Engine
The React Compiler uses a sophisticated inference engine to transform standard JavaScript into optimized, memoized code. By analyzing how values flow through your components, the engine determines when it is safe to cache values and when it is necessary to recompute them.
Overview of Inference
Unlike manual memoization (e.g., useMemo, useCallback), where developers must manually specify dependency arrays, the React Compiler infers these dependencies through static analysis. The engine performs three primary types of analysis:
- Type Inference: Determining if a value is a primitive, an object, or a function.
- Escape Analysis: Identifying which values "escape" the local scope (e.g., returned from a component or passed to a hook).
- Mutation and Aliasing Tracking: Determining if changing one variable affects another via shared references.
Escape Analysis
A core responsibility of the inference engine is identifying "escaping" values. A value is considered to have escaped if its lifetime extends beyond the immediate execution of the function. In React, values escape in two primary ways:
- Returns: The value is returned directly or is a member of an object/array that is returned.
- Hook Arguments: The value is passed as an input to a hook (e.g.,
useEffect(callback, [dep])). Because hooks can store references internally, the compiler treats these inputs as escaping.
Example: Escaping vs. Non-Escaping
In the following example, the compiler infers that b and c must be memoized because they escape, while a may be pruned if it is not used elsewhere.
function Component(props) {
// 'a' does not escape: not returned, not passed to a hook.
const a = { id: props.id };
// 'b' escapes because it is stored in 'c'.
const b = { data: props.data };
// 'c' escapes because it is returned.
const c = [b];
return c;
}
Mutation and Aliasing Effects
The engine tracks "aliasing"—when multiple variables point to the same underlying data. If a value is mutated, the engine ensures that all variables aliasing that value are also treated as updated.
To maintain correctness, the compiler groups values whose mutations interleave into a single Reactive Scope. If any value within that scope escapes, the entire scope is preserved.
Dependency Preservation
Even if a value does not escape, the engine may choose to memoize it if it serves as a dependency for an escaping value. This prevents "cache invalidation cascades" where a non-memoized primitive triggers re-renders of expensive downstream components.
function Component(props) {
// 'a' doesn't escape, but it's a dependency for 'b'.
// To keep 'b' stable, 'a' must also be memoized.
const a = [props.a];
const b = [];
b.push(a); // 'a' is now an alias within 'b'
return b; // 'b' escapes
}
Validation and Constraints
To perform accurate inference, the compiler enforces specific coding patterns. One of the most critical is the distinction between Components and Standard Functions.
Capitalized Calls
The compiler reserves capitalized function names for React Components. Components must be invoked using JSX (<MyComponent />) rather than direct function calls (MyComponent()).
If the inference engine detects a capitalized function call, it will throw a validation error. This ensures that the compiler doesn't accidentally treat a component as a regular function, which would break Hook rules and state preservation.
// ❌ Error: Capitalized functions are reserved for components
function Component() {
const x = SomeFunc();
return <div>{x}</div>;
}
// ✅ Correct: Render as JSX or rename to lowercase
function Component() {
return <SomeFunc />;
}
Inference Modes
The compiler can be configured via compilationMode to change how aggressively it applies inference:
infer(Default): The compiler automatically determines what to memoize based on escape analysis.validate: The compiler checks for manual memoization consistency (e.g., ensuringuseMemodependencies are correct) without necessarily applying new optimizations.
By understanding the "flow" of data rather than just the syntax, the inference engine allows you to write standard JavaScript while the compiler handles the complexities of React performance optimization.