compiler/packages/babel-plugin-react-compiler/docs/passes/24-pruneNonReactiveDependencies.md MARKDOWN 139 lines View on github.com → Search inside
1# pruneNonReactiveDependencies23## File4`src/ReactiveScopes/PruneNonReactiveDependencies.ts`56## Purpose7This pass removes dependencies from reactive scopes that are guaranteed to be **non-reactive** (i.e., their values cannot change between renders). This optimization reduces unnecessary memoization invalidations by ensuring scopes only depend on values that can actually change.89The pass complements `PropagateScopeDependencies`, which infers dependencies without considering reactivity. This subsequent pruning step filters out dependencies that are semantically constant.1011## Input Invariants12- The function has been converted to a ReactiveFunction structure13- `InferReactivePlaces` has annotated places with `{reactive: true}` where values can change14- Each `ReactiveScopeBlock` has a `scope.dependencies` set populated by `PropagateScopeDependenciesHIR`15- Type inference has run, so identifiers have type information for `isStableType` checks1617## Output Guarantees18- **Non-reactive dependencies removed**: All dependencies in `scope.dependencies` are reactive after this pass19- **Scope outputs marked reactive if needed**: If a scope has any reactive dependencies remaining, all its outputs are marked reactive20- **Stable types remain non-reactive through property loads**: When loading properties from stable types (like `useReducer` dispatch functions), the result is not added to the reactive set2122## Algorithm2324### Phase 1: Collect Reactive Identifiers25The `collectReactiveIdentifiers` helper builds the initial set of reactive identifiers by:261. Visiting all places in the ReactiveFunction272. Adding any place marked `{reactive: true}` to the set283. For pruned scopes, adding declarations that are not primitives and not stable ref types2930### Phase 2: Propagate Reactivity and Prune Dependencies31The main `Visitor` class traverses the ReactiveFunction and:32331. **For Instructions** - Propagates reactivity through data flow:34   - `LoadLocal`: If source is reactive, mark the lvalue as reactive35   - `StoreLocal`: If source value is reactive, mark both the local variable and lvalue as reactive36   - `Destructure`: If source is reactive, mark all pattern operands as reactive (except stable types)37   - `PropertyLoad`: If object is reactive AND result is not a stable type, mark result as reactive38   - `ComputedLoad`: If object OR property is reactive, mark result as reactive39402. **For Scopes** - Prunes non-reactive dependencies and propagates outputs:41   - Delete each dependency from `scope.dependencies` if its identifier is not in the reactive set42   - If any dependencies remain after pruning, mark all scope outputs as reactive4344### Key Insight: Stable Types45The pass leverages `isStableType` to prevent reactivity from flowing through certain React-provided stable values:4647```typescript48function isStableType(id: Identifier): boolean {49  return (50    isSetStateType(id) ||       // useState setter51    isSetActionStateType(id) || // useActionState setter52    isDispatcherType(id) ||     // useReducer dispatcher53    isUseRefType(id) ||         // useRef result54    isStartTransitionType(id) ||// useTransition startTransition55    isSetOptimisticType(id)     // useOptimistic setter56  );57}58```5960## Edge Cases6162### Unmemoized Values Spanning Hook Calls63A value created before a hook call and mutated after cannot be memoized. However, if it's non-reactive, it still should not appear as a dependency of downstream scopes.6465### Stable Types from Reactive Containers66When `useReducer` returns `[state, dispatch]`, `state` is reactive but `dispatch` is stable. The pass correctly handles this.6768### Pruned Scopes with Reactive Content69The `CollectReactiveIdentifiers` pass also examines pruned scopes and adds their non-primitive, non-stable-ref declarations to the reactive set.7071### Transitive Reactivity Through Scopes72When a scope retains at least one reactive dependency, ALL its outputs become reactive.7374## TODOs75None in the source file.7677## Example7879### Fixture: `unmemoized-nonreactive-dependency-is-pruned-as-dependency.js`8081**Input:**82```javascript83function Component(props) {84  const x = [];85  useNoAlias();86  mutate(x);8788  return <div>{x}</div>;89}90```9192**Before PruneNonReactiveDependencies:**93```94scope @2 dependencies=[x$15_@0:TObject<BuiltInArray>] declarations=[$23_@2]95```9697**After PruneNonReactiveDependencies:**98```99scope @2 dependencies=[] declarations=[$23_@2]100```101102The dependency on `x` is removed because `x` is created locally and therefore non-reactive.103104### Fixture: `useReducer-returned-dispatcher-is-non-reactive.js`105106**Input:**107```javascript108function f() {109  const [state, dispatch] = useReducer();110111  const onClick = () => {112    dispatch();113  };114115  return <div onClick={onClick} />;116}117```118119**Generated Code:**120```javascript121function f() {122  const $ = _c(1);123  const [, dispatch] = useReducer();124  let t0;125  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {126    const onClick = () => {127      dispatch();128    };129    t0 = <div onClick={onClick} />;130    $[0] = t0;131  } else {132    t0 = $[0];133  }134  return t0;135}136```137138The `onClick` function only captures `dispatch`, which is a stable type. Therefore, `onClick` is non-reactive, and the JSX element can be memoized with zero dependencies.

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.