Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Experiment] Context Selectors #20646

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Commits on Jul 10, 2021

  1. Extract early bailout to separate function

    This block is getting hard to read so I moved it to a separate function.
    I'm about to refactor the logic that wraps around this path.
    
    Ideally this early bailout path would happen before the begin phase
    phase. Perhaps during reconcilation of the parent fiber's children.
    acdlite committed Jul 10, 2021
    Configuration menu
    Copy the full SHA
    bfabd88 View commit details
    Browse the repository at this point in the history
  2. Extract state and context check to separate function

    The only reason we pass `updateLanes` to some begin functions is to
    check if we can perform an early bail out. But this is also available
    as `current.lanes`, so we can read it from there instead.
    
    I think the only reason we didn't do it this way originally is because
    components that have two phases — error and Suspense boundaries —
    use `workInProgress.lanes` to prevent a bail out, since during the
    initial render there is no `current`. But we can check the `DidCapture`
    flag instead, which we use elsewhere to detect the second phase.
    acdlite committed Jul 10, 2021
    Configuration menu
    Copy the full SHA
    ed429fc View commit details
    Browse the repository at this point in the history
  3. [Experiment] Context Selectors

    For internal experimentation only.
    
    This implements `unstable_useContextSelector` behind a feature flag.
    It's based on [RFC 119](reactjs/rfcs#119) and
    [RFC 118](reactjs/rfcs#118) by @gnoff.
    
    Usage:
    
    ```js
    const context = useContextSelector(Context, c => c.selectedField);
    ```
    
    The key feature is that if the selected value does not change between
    renders, the component will bail out of rendering its children, a la
    `memo`, `PureComponent`, or the `useState` bailout mechanism. (Unless
    some other state, props, or context was updated in the same render.)
    
    One difference from the RFC is that it does not return the selected
    value. It returns the full context object. This serves a few purposes:
    it discourages you from creating any new objects or derived values
    inside the selector, because it'll get thrown out regardless. Instead,
    all the selector will do is return a subfield. Then you can compute
    the derived value inside the component, and if needed, you memoize that
    derived value with `useMemo`.
    
    If all the selectors do is access a subfield, they're (theoretically)
    fast enough that we can call them during the propagation scan and bail
    out really early, without having to visit the component during the
    render phase.
    
    Another benefit is that it's API compatible with `useContext`. So we can
    put it behind a flag that falls back to regular `useContext`.
    
    The longer term vision is that these optimizations (in addition to other
    memoization checks, like `useMemo` and `useCallback`) are inserted
    automatically by a compiler. So you would write code like this:
    
    ```js
    const {a, b} = useContext(Context);
    const derived = computeDerived(a, b);
    ```
    
    and it would get converted to something like this:
    
    ```js
    const {a} = useContextSelector(Context, context => context.a);
    const {b} = useContextSelector(Context, context => context.b);
    const derived = useMemo(() => computeDerived(a, b), [a, b]);
    ```
    
    (Though not this exactly. Some lower level compiler output target.)
    acdlite committed Jul 10, 2021
    Configuration menu
    Copy the full SHA
    ae128b3 View commit details
    Browse the repository at this point in the history
  4. Move API to unstable useContext option

    This will to make it easier to A/B test, or to revert if we abandon the
    experiment. Using a selector will not change the return type of
    `useContext`. Use a userspace hook to get the selected value:
    
    ```js
    function useContextSelector<C, S>(Context: C, selector: C => S): S {
      const context = useContext(Context, {unstable_selector: selector});
      const selectedContext = selector(context);
      return selectedContext;
    }
    ```
    acdlite committed Jul 10, 2021
    Configuration menu
    Copy the full SHA
    85dc024 View commit details
    Browse the repository at this point in the history
  5. Configuration menu
    Copy the full SHA
    c0b8b58 View commit details
    Browse the repository at this point in the history