本文正在参加「金石计划」
对于拥有许多状态更新逻辑的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 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 不是纯函数