你不知道的React系列(十七)useReducer(掌握)

71 阅读2分钟

本文正在参加「金石计划」

对于拥有许多状态更新逻辑的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 reducer

const [state, dispatch] = useReducer(reducer, initialArg, init?);
// reducer:(state, action) => newState ========> action:{type:string, payload:string} 纯函数
// initialArg: 初始state
// init: state 将被设置为 init(initialArg)
  • reducer 纯函数
  • init 没有设置 state 初始化为 initialArg,指定了 state 初始化为 init(initialArg)

dispatch function

接收 action 更新 state 重新渲染

  • 使用场景

    • state 逻辑较复杂且包含多个子值

    • 下一个 state 依赖于之前的 state

步骤

  • 将设置状态的逻辑 修改 成 dispatch 的一个 action;

  • 编写 一个 reducer 函数;

  • 在你的组件中 使用 reducer。

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}
  • 指定初始化值

    const [state, dispatch] = useReducer(reducer, {count: initialCount});
    
  • 惰性初始化

    计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理

    function init(initialCount) {
      return { count: initialCount };
    }
    function reducer(state, action) {
      switch (action.type) {
        case "increment":
          return { count: state.count + 1 };
        case "decrement":
          return { count: state.count - 1 };
        case "reset":
          return init(action.payload);
        default:
          throw new Error();
      }
    }
    
    function Counter({ initialCount }) {
      const [state, dispatch] = useReducer(reducer, initialCount, init);
      return (
        <>
          Count: {state.count}
          <button
            onClick={() => dispatch({ type: "reset", payload: initialCount })}
          >
            Reset
          </button>
          <button onClick={() => dispatch({ type: "decrement" })}>-</button>
          <button onClick={() => dispatch({ type: "increment" })}>+</button>
        </>
      );
    }
    
  • 跳过 dispatch

    如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及effect 的执行。

对比 useState 和 useReducer 

  • 代码体积  useReducer 可以减少代码量。

  • 可读性  useReducer 允许你将状态更新逻辑与事件处理程序分离开来。

  • 可调试性  可以在 reducer 函数中通过打印日志的方式来观察每个状态的更新,以及为什么要更新(来自哪个 action)。 如果所有 action 都没问题,你就知道问题出在了 reducer 本身的逻辑中。

  • 可测试性  reducer 是一个不依赖于组件的纯函数。这就意味着你可以单独对它进行测试。

  • 个人偏好

注意

  • reducers 必须是纯粹的。

  • 每个 action 都描述了一个单一的用户交互,即使它会引发数据的多个变化。  举个例子,如果用户点击 重置按钮,dispatch 一个 reset_form 的 action 比 dispatch 多个单独的 set_field 的 action 更加合理。

问题

  • dispatch action,console.log 是原来的值

    调用 reducer 获取

    const action = { type: 'incremented_age' };
    dispatch(action);
    
    const nextState = reducer(state, action);
    console.log(state);
    console.log(nextState);
    
  • dispatch action,视图没有更新

    对象和数组更新方式错误

  • state 部分变为 undefined

    对象更新方式错误

  • state 变为 undefined

    reducer 函数没有 return

  • Too many re-renders

    事件处理函数使用错误

  • reducer 或 initializer 函数执行两次

    component, initializer, and reducer functions 不是纯函数