SoFunction
Updated on 2025-04-07

Some optimization suggestions for React using Context

Common APIs

const MyContext = (defaultValue);

Create a Context object. When React renders a component that has subscribed to this Context object, this component will match the closest to it from the component tree.ProviderThe current context value is read in  .

< value={/* A value */}>

Each Context object returns a Provider React component that allows consuming components to subscribe to changes in context.

Provider receives onevalueProperties, passed to the consumer component. A Provider can correspond to multiple consumer components. Multiple providers can also be used in nesting, and the inner layer will cover the outer layer of data.

useContext

const store = useContext(MyContext)

Receive a context object () and return the current value of the context. The current context value is the closest to the current component in the upper component<>ofvalueprop decide.

When the value of the Provider changes, all consumer components inside it will be re-rendered

After understanding the API, let’s take a look at a simple example.

Example

const MyContext = (null);

function reducer(state, action) {
  switch () {
    case 'addCount': {
      return {
        ...state,
        count:  + 1
      }
    }
    case 'addNum': {
      return {
        ...state,
        num:  + 1
      }
    }
    default: return state;
  }
}

const MyProvider = ({ children }) => {
  const [store, dispatch] = useReducer(reducer, { count: 0, num: 0 })
  return < value={{store, dispatch}}>{children}</>
};

export default () => {
  return (
    <MyProvider>
      <ChildCount />
      <ChildNum />
      <Child  />
    </MyProvider>
  );
}

export default () => {
    const { state, dispatch } = (MyContext);
    ('re-render ChildCount', )
    return (
      <>
        <div>count is: {}</div>
        <button onClick={() => dispatch({ type: 'addCount' })}> AddCount </button>
      </>
    )
}

export default () => {
    const { state, dispatch } = (MyContext);
    ('re-render ChildNum', )
    return (
      <>
        <div>num is: {}</div>
        <button onClick={() => dispatch({ type: 'addNum' })}> AddNum </button>
      </>
    )
}

export default () => {
  ('re-render Child')
  return <div>Child</div>
}

ClickAddCountButton, output:

re-render ChildCount 1re-render ChildNum 0

ClickAddNumButton, output:

re-render ChildCount 1re-render ChildNum 1

We can find thatAll consumer components underAfter the change,re-render

Changecount 、numAny value,ChildCount,ChildNumCityre-render

For the abovere-renderThe following solutions can be optimized

optimization

Function memory for subcomponents

We modify all Child components as follows

export default (() => {
    const { state, dispatch } = (MyContext);
    ('re-render ChildCount', )
    return (
      <>
        <div>count is: {}</div>
        <button onClick={() => dispatch({ type: 'addCount' })}> AddCount </button>
      </>
    )
})

ClickAddCountAfter finding out, it still prints out

re-render ChildCount 1
re-render ChildNum 0

Let's get to know each other again

By default, only the incoming props are compared. If it is an internal state update (useState, useContext, etc.), it will still be re-rendered. In the above example, the state returned by useContext is constantly changing, resulting in the update even if the component wrapped by memo is still triggered.

useMemo

We modify all Child components as follows

export default () => {
    const { state, dispatch } = (MyContext);
    return useMemo(() => {
      ('re-render ChildCount', )
      return (
          <>
            <div>count is: {}</div>
            <button onClick={() => dispatch({ type: 'addCount' })}> AddCount </button>
          </>
      )
    }, [, dispatch])
}

ClickaddCountAfter finding out, only printed out

re-render ChildCount 1

ClickaddNumAfter finding out, only printed out

re-render ChildNum 1

useMemo can be used to make finer granular caches, and we can manage whether the component is updated in dependency arrays.

We can think about whether there is a way to render on demand without usingMemo. Implement on-demand rendering just like useSelector in react-redux

Hands-on useSelector

Let's think about it first, in the example above, trigger the subcomponentre-renderWhat is the reason?

That's right, it's becauseThe value is constantly changing, so we need to find a way to make the child components unable to perceivevalueChanges, at the same timevalueWhen a certain value changes, consumption can be triggeredvalueSubcomponents ofre-render

We use the Observer Mode to implement it

1. We useuseMemoCaches the first value so that the child components cannot perceive the change in value

2. If the value does not change, the subcomponent will notre-render, at this time we need to change the value,re-renderSubcomponent, we need ahooks(useSelector)Help us implement subcomponentsre-render

3. When the subcomponent is initialized,useSelectorTo help it subscribe to the callback function of state changed and return the latest state (the function gets the two states inside for comparison, and if the different ones, the component will be updated)

4. InCreate a collection of subcomponents that collect state change callbacks and listen insidestate(value), if changed, iterates over the collection and executes all callback functions

Based on the above, we have implemented it in turn, useSelector, useDispatch

const MyProvider = ({children}) =&gt; {
  const [state, dispatch] = useReducer(reducer, initState);
  
  // ref state
  const stateRef = useRef(null);
   = state;

  // ref subscribe to callback array  const subscribersRef = useRef([]);

  // state changes, traversal execution callback  useEffect(() =&gt; {
    (sub =&gt; sub());
  }, [state]);

  // Cache value, use ref to get the latest state, subscribe status  const value = useMemo(
    () =&gt; ({
      dispatch,
      subscribe: cb =&gt; {
        (cb);
        return () =&gt; {
           = (item =&gt; item !== cb);
        };
      },
      getState: () =&gt; 
    }),
    []
  )

  return &lt; children={children} value={value} /&gt;;
}

useSelector

export const useSelector = selector =&gt; {
  // Force update  const [, forceRender] = useReducer(v =&gt; v + 1, 0);
  const store = useContext(MyContext);

  // Get the current state  const selectedStateRef = useRef(null)
   = selector(());

  // Compare update callback  const checkForUpdates = useCallback(() =&gt; {
    // Get the changed state    const newState = selector(());
    // Compare the two before and after states    if (newState !== ) forceRender({});
  }, [store]);
  
  // Subscribe to state  useEffect(() =&gt; {
    const subscription = (checkForUpdates);
    return () =&gt; subscription();
  }, [store, checkForUpdates]);
  
  // Return the required state  return ;
}

useDispatch

export const useDispatch = () => {
  const store = useContext(MyContext);
  return 
}

We use the API rewritten above to rewrite the first example

export default () => {
    return (
      <MyProvider>
        <ChildCount />
        <ChildNum />
        <Child />
      </Provider>
    );
}

export default () => {
    const dispatch = useDispatch();
    const count = useSelector(state => );
    ('re-render ChildCount', count)
    return (
      <>
        <div>count is: {count}</div>
        <button onClick={() => dispatch({ type: 'addCount' });}> AddCount </button>
      </>
    )
};

export default () => {
    const dispatch = useDispatch();
    const num = useSelector(state => );
    ('re-render ChildNum', num)
    return (
      <>
        <div>num is: {num}</div>
        <button onClick={() => dispatch({ type: 'addNum' });}> AddNum </button>
      </>
    )
}

export default () => {
    ('re-render Child')
    return <div>Child</div>
}

ClickAddCount: Only re-render ChildCount 1 was printed

ClickAddNum: Only re-render ChildNum 1 was printed

Through some thoughts on the use of Context, we simply implemented the useSelector and implemented the on-demand rendering of the Context component.

Summarize

When using the Context API, avoid unnecessaryre-render, can be useduseMemoDo fine-grained updates, you can also useuseSelectorImplement on-demand rendering

The above is the detailed content of some optimization suggestions for React using Context. For more information about React Context, please pay attention to my other related articles!