This is the first day of my participation in the Gwen Challenge in November. Check out the details: the last Gwen Challenge in 2021
UseReducer is an alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method. UseReducer can be more useful than useState in some situations, such as when the state logic is complex and contains multiple subvalues, or when the next state depends on the previous state.
What scenarios are more suitable for useReducer or useState? The following are some examples.
Single state management
useuseState
Implement custom change theme colorhook
- Window.matchmedia (mediaQueryString) obtains media query results through JS features
- parameter
mediaQueryString
forprefers-color-scheme:dark
Is used to detect whether the user has set the theme color of the system to light or dark.Refer to the MDN - If the user preferences are set to
dark
Mode, then passsetMode
The initialization mode isdark
, or forlight
And saved inlocalStorage
;
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]
}
Copy the code
useuseReducer
Implement custom change theme colorhook
- There are two obvious implementations
useState
It’s a little bit more straightforward
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]
}
Copy the code
Conclusion: In this scenario where you only need to manage one variable, it is better to use useState
Multiple states are managed, with one state dependent on another
useuseState
implementationuseUndo
- UseUndo is an example on gitHub
- In this case,
past
Update dependency ofpresent
And thepresent
Update dependency offuture
They are interdependent, so useuseState
When, a little attention is easy to appear the situation of the loop.
function useUndo(initialPresent) {
const [state, setState] = React.useState({
past: [].present: initialPresent,
future: [],})constcanUndo = state.past.length ! = =0
constcanRedo = 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}]
}
Copy the code
useuseReducer
implementationuseUndo
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: [],})constcanUndo = state.past.length ! = =0
constcanRedo = 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}]
}
Copy the code
In this case, the reducer is used to manage events separately. The code of useUndo itself is much simpler and the processing logic is mainly in the Reducer function.
Conclusion: It is better to use useReducer when your state is dependent on other states
Complex business logic scenario: click a buttonThe next page
Using useState
// useStatesetSate(... page, currPage +1)
Copy the code
Using useReducer
// action
dispatch({ type: 'GO_TO_NEXT_PAGE' })
// reducer
case 'GO_TO_NEXT_PAGE':
return { ...state, page: currPage + 1}
Copy the code
UseState looks simpler, but in the future if additional actions need to be added when the user clicks “Next page”, the reducer needs to be updated and the useReducer will make the logic clearer.
Conclusion: It is better to use useReducer for complex logic processing
conclusion
- Single state management use
useState
Simple and clear - Used when one state depends on another
useReducer
- Complex business logic scenarios
useReducer
The logic is clearer and easier to maintain
Other suitable useReducer scenarios:
- JavaScript objects or arrays as states
- The state needs to be updated deep in the component tree
- More convenient
debug
, better predictive and maintainable state
When to use useState and when to use useReducer is not black and white. In most cases, the two methods can be used interchangeably.
reference
MDN-matchMedia
Should I useState or useReducer?
JavaScript Ramblings
useReducer vs useState in React