define

An alternative to useState(). It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method

usage

const [state, dispatch] = useReducer(reducer, initialArg, init);
Copy the code

Usage scenarios

  • When multiple states need to be updated together
  • When the state update logic is complicated
  • The current state depends on the previous state, which is writtensetState(prevState => newState)when

Including but not limited to the above three.

The advantages of useReducer() over useState() when used in the above scenario

  1. In the above scenario, the benefits of using useReducer() are analyzed:

    • Reducer can better describe “how to update status” than useState. For example, Reducer can read related states and update multiple states simultaneously.
    • Decoupling mode [component is responsible for issuing actions and Reducer is responsible for updating status],Makes the code logic clearer. Code behavior is more predictable (e.g.useEffectMore stable update timing)
    • By passing dispatches, you can reduce the passing of state values. useReducerAlways return the samedispatchFunction, which is a sign of complete decoupling: the status update logic can be arbitrarily changed while initiatedactionThe channels remain the same.
    • Because of the previous decoupling pattern,useEffectThe body of the function,callback functionYou just need to usedispatchTo issue aactionAnd theThere is no need to rely directly on state values. As a result,inuseEffect,useCallback,useMemothedepsArrays do not need to contain status values, reducing the need for them to be updated. Not only improves readability, but also improves performance (useCallback,useMemoAn update to a component usually results in a refresh of the child component.
  2. Example comparison: useState() and useReducer() methods

    • Count using the useState() method
     Use the useState() method
     function Counter() {
       const [count, setCount] = useState(0);
       const [step, setStep] = useState(1);
    
       useEffect(() = > {
         const id = setInterval(() = > {
           setCount(c= > c + step); // Rely on other states to update
         }, 1000);
         return () = > clearInterval(id);
         // To ensure that steps in setCount are up to date,
         // We also need to specify step in the DEps array
       }, [step]);
    
       return (
         <>
           <h1>{count}</h1>
           <input value={step} onChange={e= > setStep(Number(e.target.value))} />
         </>
       );
     }
    Copy the code

    Explanation: As the number of interdependent states increases, the logic of setState becomes more and more complex, as does the DEPS array of useEffect. As readability decreases, the timing of useEffect re-execution becomes more unpredictable.

    • Count using the useReducer() method
    function Counter() {
      const initialState = {
        count: 0.step: 1};function reducer(state, action) {
        console.log(state, action)
        const { count, step } = state;
        if (action.type === 'tick') {
          return { count: count + step, step };
        } else if (action.type === 'step') {
          return { count, step: action.step };
        } else {
          throw new Error();
        }
      }
      const [state, dispatch] = useReducer(reducer, initialState);
      const { count, step } = state;
    
      useEffect(() = > {
        const id = setInterval(() = > {
          dispatch({ type: 'tick' });
          }, 1000);
        return () = > clearInterval(id); } []);// The deps array does not need to contain step
    
      return (
        <>
          <h1>count:{count}, step: {step}</h1>
          <input value={step} onChange={(e)= > dispatch({ type: 'step', step: Number(e.target.value) })} />
        </>)}Copy the code

    The useReducer() method allows a component to issue an action without knowing how to update its status. In addition, the step update will not cause useEffect invalidation or re-execution. This is because useEffect depends on Dispatch, not the state value in DEPS

Explain the usage of inline Reducer

The Reducer declaration can be made inside the component to access props via closures, as well as the previous hooks result:


function Counter({ step }) {
  const [count, dispatch] = useReducer(reducer, 0);
  function reducer(state, action) {
    if (action.type === 'tick') {
      // You can access any variable inside a component through a closure
      // Includes props, as well as the result of hooks before useReducer
      return state + step;
    } else {
      throw new Error(a); } } useEffect(() = > {
    const id = setInterval(() = > {
      dispatch({ type: 'tick' });
    }, 1000);
    return () = > clearInterval(id); } []);return <h1>{count}</h1>;
}
Copy the code

Explanation:

  • A button is clicked, its onClick is called, dispatch({type:’add’}) is executed, and the React framework schedules an update
  • The React framework processes the update just scheduled and starts rerendering the component tree
  • When rendering to the useReducer of the Counter component, call reducer(prevState, {type:’add’}) to process the previous actions

The important difference is that the Reducer is called at the next render, and its closure captures the props for the next render.