这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战
useReducer是useState的替代方案。它接收一个形如(state, action) => newState的reducer,并返回当前的state以及与其配套的dispatch方法。在某些场景下,useReducer会比useState更适用,例如state逻辑较复杂且包含多个子值,或者下一个state依赖于之前的state等。
具体什么样的场景useReducer或者useState更适用,以下从一些列子来分析。
单一状态管理
使用useState实现改变主题色的自定义hook
window.matchMedia(mediaQueryString)通过js方式来获得媒体查询结果的特性- 参数
mediaQueryString为prefers-color-scheme:dark时:代表用于检测用户是否有将系统的主题色设置为亮色或者暗色作用,参考MDN - 用户如果设置偏好为
dark模式,那么通过setMode初始化模式为dark,否则为light,并且保存在localStorage中;
function useDarkMode() {
const preferDarkQuery = '(prefers-color-scheme: dark)'
const [mode, setMode] = React.useState(
() =>
window.localStorage.getItem('colorMode') ||
(window.matchMedia(preferDarkQuery).matches ? 'dark' : 'light'),
)
React.useEffect(() => {
const mediaQuery = window.matchMedia(preferDarkQuery)
const handleChange = () => setMode(mediaQuery.matches ? 'dark' : 'light')
mediaQuery.addListener(handleChange)
return () => mediaQuery.removeListener(handleChange)
}, [])
React.useEffect(() => {
window.localStorage.setItem('colorMode', mode)
}, [mode])
return [mode, setMode]
}
使用useReducer实现改变主题色的自定义hook
- 可以比较明显的看到两种实现方式
useState会更简单明了一些
const preferDarkQuery = '(prefers-color-scheme: dark)'
function darkModeReducer(state, action) {
switch (action.type) {
case 'MEDIA_CHANGE': {
return {...state, mode: action.mode}
}
case 'SET_MODE': {
return {...state, mode: action.mode}
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
function init() {
return {
mode:
window.localStorage.getItem('colorMode') ||
(window.matchMedia(preferDarkQuery).matches ? 'dark' : 'light'),
}
}
function useDarkMode() {
const [state, dispatch] = React.useReducer(
darkModeReducer,
{mode: 'light'},
init,
)
const {mode} = state
React.useEffect(() => {
const mediaQuery = window.matchMedia(preferDarkQuery)
const handleChange = () =>
dispatch({
type: 'MEDIA_CHANGE',
mode: mediaQuery.matches ? 'dark' : 'light',
})
mediaQuery.addListener(handleChange)
return () => mediaQuery.removeListener(handleChange)
}, [])
React.useEffect(() => {
window.localStorage.setItem('colorMode', mode)
}, [mode])
const setMode = React.useCallback(
newMode => dispatch({type: 'SET_MODE', mode: newMode}),
[],
)
return [mode, setMode]
}
结论:在这种只需要管理一个变量的场景下,使用
useState会更好一些
多个状态管理,且一个状态依赖另一个状态
使用useState实现useUndo
- useUndo是`gitHub上的一个例子
- 在这个例子中,
past的更新依赖present,而present的更新依赖future,他们是相互依赖的,所以使用useState时,稍微不注意很容易出现死循环的情况。
function useUndo(initialPresent) {
const [state, setState] = React.useState({
past: [],
present: initialPresent,
future: [],
})
const canUndo = state.past.length !== 0
const canRedo = state.future.length !== 0
const undo = React.useCallback(() => {
setState(currentState => {
const {past, present, future} = currentState
if (past.length === 0) return currentState
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [present, ...future],
}
})
}, [])
const redo = React.useCallback(() => {
setState(currentState => {
const {past, present, future} = currentState
if (future.length === 0) return currentState
const next = future[0]
const newFuture = future.slice(1)
return {
past: [...past, present],
present: next,
future: newFuture,
}
})
}, [])
const set = React.useCallback(newPresent => {
setState(currentState => {
const {present, past} = currentState
if (newPresent === present) return currentState
return {
past: [...past, present],
present: newPresent,
future: [],
}
})
}, [])
const reset = React.useCallback(newPresent => {
setState(() => ({
past: [],
present: newPresent,
future: [],
}))
}, [])
return [state, {set, reset, undo, redo, canUndo, canRedo}]
}
使用useReducer实现useUndo
const UNDO = 'UNDO'
const REDO = 'REDO'
const SET = 'SET'
const RESET = 'RESET'
function undoReducer(state, action) {
const {past, present, future} = state
const {type, newPresent} = action
switch (type) {
case UNDO: {
if (past.length === 0) return state
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [present, ...future],
}
}
case REDO: {
if (future.length === 0) return state
const next = future[0]
const newFuture = future.slice(1)
return {
past: [...past, present],
present: next,
future: newFuture,
}
}
case SET: {
if (newPresent === present) return state
return {
past: [...past, present],
present: newPresent,
future: [],
}
}
case RESET: {
return {
past: [],
present: newPresent,
future: [],
}
}
}
}
function useUndo(initialPresent) {
const [state, dispatch] = React.useReducer(undoReducer, {
past: [],
present: initialPresent,
future: [],
})
const canUndo = state.past.length !== 0
const canRedo = state.future.length !== 0
const undo = React.useCallback(() => dispatch({type: UNDO}), [])
const redo = React.useCallback(() => dispatch({type: REDO}), [])
const set = React.useCallback(
newPresent => dispatch({type: SET, newPresent}),
[],
)
const reset = React.useCallback(
newPresent => dispatch({type: RESET, newPresent}),
[],
)
return [state, {set, reset, undo, redo, canUndo, canRedo}]
}
这样看的话,使用reducer单独管理事件,useUndo本身的代码简单很多,处理逻辑主要在reducer函数中。
结论:当你的状态依赖与其它状态时,使用
useReducer会更好一些
复杂业务逻辑场景:点击按钮下一页
使用useState
// useState
setSate(...page, currPage + 1)
使用useReducer
// action
dispatch({ type: 'GO_TO_NEXT_PAGE' })
// reducer
case 'GO_TO_NEXT_PAGE':
return { ...state, page: currPage + 1}
看起来useState更简单一些,但是以后如果在用户单击“下一页”按钮时需要增加执行其他操作,那则只需更新reducer就行了,此时useReducer会使得逻辑更清晰一些。
结论:复杂逻辑处理,使用
useReducer好一些
总结
- 单一状态管理使用
useState简单明了 - 一个状态依赖另一个状态时使用
useReducer - 复杂业务逻辑场景
useReducer是逻辑更加清晰,便于维护 其它适合useReducer场景: - JavaScript 对象或数组作为状态
- 需要在组件树的深处更新状态
- 更方便的
debug,更好的预测和可维护状态 其实什么时候用useState,什么时候用useReducer不是非黑即白,大部分场景下,两种方法可以互相替换使用。
参考
MDN-matchMedia
Should I useState or useReducer?
JavaScript Ramblings
useReducer vs useState in React