useState 和 useReducer 的功能非常相似,都是负责函数组件的状态管理。为什么要出两个功能类似的API呢?
useState: 细粒度state
众所周知,以前用类组件开发时, 组件的整个state都要放在一个对象中,造成耦合 useState的出现就是为了更细粒度的分开state,使开发更清晰
useReducer:低成本的数据流
定义
const [state, dispatch] = useReducer(reducer, initialArg, init);
使用方式
不带初始化函数
const [t, dispatchT] = useReducer(reducer, 1)
console.log(t) // 1
如果初始值较为复杂,可以带一个初始化函数
const [t, dispatchT] = useReducer(reducer, 1, (n)=> n + 3)
console.log(t) // 4
官方例子
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>
</>
);
}
使用场景
在某些场景下,useReducer
会比 useState
更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer
还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch
而不是回调函数 。
useReducer + useContext 来做局部数据流
useReducer 更适合拿来做简单场景下的数据流。useReducer 是阉割版的 redux,只缺了一个状态共享能力,用 hooks 的 useContext 刚刚好。
官方文档中推荐的使用场景是“deep update”,从某种程度来说就是一个局部数据流:
function Parent() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<context.Provider value={dispatch}>
<DeepTree />
</context.Provider>
);
}
如果该数据流中也要用到 state,可以把 state 和 dispatch 用两个 context 往下传递,原因是这样可以避免只依赖了 dispatch 的组件受到 state 更新的影响。
function Parent() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<context1.Provider value={state}>
<context2.Provider value={dispatch}>
<DeepTree />
</context2.Provider>
</context1.Provider>
);
}
从上面两端代码就可以看出,在函数组件中实现一套看起来还可以的数据流是一件很简单的事情。
useState跟useReducer的关系
useState是useReducer的子集,可以用useReducer实现一个useState
const useMyState = (initState) => {
let init = initState
if(typeof initState === 'function') {
init = initState()
}
// 一个特殊的reducer
const reducer = (state, action) => {
if (typeof action === 'function') {
return action(state)
}
return action
}
const [state, dispatch] = useReducer(reducer, init)
const setState = useCallback((action) => {
dispatch(action)
}, [])
return [state, setState]
}
// 使用方式
const [x, setX] = useMyState(4)