定义
useState() 的替代方案。它接收一个形如
(state, action) => newState的 reducer,并返回当前state以及与之配套的dispatch方法
用法
const [state, dispatch] = useReducer(reducer, initialArg, init);
使用场景
- 当多个 state 需要一起更新时
- 当state 更新逻辑较复杂
- 当下一个 state 依赖于之前的 state,即 编写
setState(prevState => newState)时 包括但不限于以上三种。
在以上场景中使用时,useReducer()相对于 useState() 的优势
-
上述场景中,使用 useReducer()的好处分析:
- reducer 相对于 useState 可以更好的描述“如何更新状态”。 比如:reducer 能够读取相关的状态、同时更新多个状态。
- 【组件负责发出 action,reducer 负责更新状态】的解耦模式, 使得代码逻辑更加清晰。代码行为更加可以预测(比如:
useEffect的更新时机更加稳定) - 通过传递 dispatch ,可以减少状态值的传递。
useReducer总是返回相同的dispatch函数,这是彻底解耦的标志:状态更新逻辑可以任意变化,而发起action的渠道始终不变。 - 因前面的解耦模式,
useEffect函数体、callback function只需要使用dispatch来发出action,而无需直接依赖状态值。因此,在useEffect、useCallback、useMemo的deps数组中无需包含状态值,也减少了它们更新的需要。不但能提高可读性,而且能提升性能(useCallback、useMemo的更新往往会造成子组件的刷新)。
-
示例对比:useState() 和 useReducer()方法
- 使用 useState()方法计数
// 使用 useState()方法 function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1); useEffect(() => { const id = setInterval(() => { setCount(c => c + step); // 依赖其他state来更新 }, 1000); return () => clearInterval(id); // 为了保证setCount中的step是最新的, // 我们还需要在deps数组中指定step }, [step]); return ( <> <h1>{count}</h1> <input value={step} onChange={e => setStep(Number(e.target.value))} /> </> ); }讲解: 随着相互依赖的状态变多,
setState的逻辑会变得越来越复杂,useEffect的 deps 数组也会变得复杂。 降低可读性的同时,useEffect重新执行的时机变得更加难以预料。- 使用 useReducer()方法计数
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); }, []); // deps数组不需要包含step return ( <> <h1>count:{count}, step: {step}</h1> <input value={step} onChange={(e) => dispatch({ type: 'step', step: Number(e.target.value) })} /> </> ) }讲解:useReducer()方法使得组件只需要发出action,而无需知道如何更新状态。另外,此时 step 的更新不会造成
useEffect的失效、重执行。因为此时的useEffect依赖于dispatch,而不是deps中的状态值
内联 reducer 的用法讲解
可以将reducer声明在组件内部,从而能够通过闭包访问props、以及前面的hooks结果:
function Counter({ step }) {
const [count, dispatch] = useReducer(reducer, 0);
function reducer(state, action) {
if (action.type === 'tick') {
// 可以通过闭包访问到组件内部的任何变量
// 包括props,以及useReducer之前的hooks的结果
return state + step;
} else {
throw new Error();
}
}
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: 'tick' });
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
讲解:
- 某个button被用户点击,它的onClick被调用,其中执行了dispatch({type:'add'}),React框架安排一次更新
- React框架处理刚才安排的更新,开始重渲染组件树
- 渲染到Counter组件的useReducer时,调用reducer(prevState, {type:'add'}) ,处理之前的action 重要的区别在于,reducer是在下次渲染的时候被调用的,它的闭包捕获到了下次渲染的props。