useReducer
UseReducer is often used in place of useState to unify state management and avoid development inconvenience caused by too many states
The sample
The useReducer receives two parameters: the Reducer function and the initial state initState
const reducer = (state,action) = > {
switch(action.type){
case 'input':
return {
...state,
[action.key]: action.value
}
}
}
const initState = {
name: ' '.age: 0
}
const App = () = >{
const [items, dispatch] = useReducer(reducer, initState);
const handleInput = (val,key) = > {
dispatch({
type: 'input'.key:key,
value: val
})
}
return (
<div>
<input placeholder={'name'}onInput={(e)= >handleInput(e.currentTarget.value,'name')}/>
<input placeholder={'age'}onInput={(e)= >handleInput(e.currentTarget.value,'age')}/>
</div>)}Copy the code
useContext
UseContext can make state passing much easier when components are deeply nested and property pass-through becomes cumbersome
// Step 1: Create the context you want to share
export const ThemeContext = React.createContext('light');
const App = () = >{
const [value,setValue] = useState('dark')
useEffect(() = >{
setTimeout(() = >setValue('blue'),2000)
},[])
// Step 2: Use the Provider to provide the value of ThemeContext. Any subtree contained by the Provider can directly access the value of ThemeContext
return (
<ThemeContext.Provider value={value}>
<Toolbar />
</ThemeContext.Provider>
);
}
// The Toolbar component does not need to transparently pass ThemeContext
function Toolbar(props) {
return <ThemedButton />;
}
function ThemedButton(props) {
// Step 3: Use shared Context
const theme = useContext(ThemeContext)
return <h1>{theme}</h1>
}
export default App
Copy the code
useReducer + useContext
UseReducer can manage state, useContext can transfer state, so combined as a small state manager.
Based on the above useContext, modify the useReducer
import React, { useReducer, useContext } from 'react'
interface IState {
theme: string
}
/** * context */
export const ThemeContext = React.createContext(null)
const initState: IState = {
theme: 'dark'
}
const reducer = (state, action) = > {
switch (action.type) {
case 'changeTheme':
return {
...state,
theme: action.val
}
default:
return state
}
}
function ThemedButton() {
const ctx = useContext(ThemeContext) || {}
const [ state = {}, dispatch = null ] = ctx
console.log(state)
const changeTheme = () = > {
dispatch({
type: 'changeTheme'.val: 'light'})}return (
<>
<h1>{ state.theme }</h1>
<button type="button" onClick={ changeTheme } >changeTheme</button>
</>)}function Toolbar(props) {
return <ThemedButton />
}
const App: React.FC = () = > {
const [state, dispatch] = useReducer(reducer, initState)
return (
<ThemeContext.Provider value={ [state.dispatch]} >
<Toolbar />
</ThemeContext.Provider>)}export default App
Copy the code
UseContext rendering problem
Any child component wrapped by context.provider that calls useContext must re-render when the Context changes, which incurs a lot of unnecessary rendering overhead. There are some solutions
Break up the context
Don’t put all the states in one context
export const ThemeContext = React.createContext(null)
export const OtherContext = React.createContext(null)
const App: React.FC = () = > {
const [state, dispatch] = useReducer(reducer, initState)
const [otherState, otherDispatch] = useReducer(otherReducer, otherInitState)
return (
<ThemeContext.Provider value={ [ state.dispatch ] }>
<OtherContext.Provider value={ [ state: otherState.dispatch: otherDispatch ] }>
<Toolbar />
<OtherComponent />
</OtherContext.Provider>
</ThemeContext.Provider>)}Copy the code
The OtherComponent only subscrires to the OtherContext, but each value passed is a new array reference, so use the memo to cache the value
export const ThemeContext = React.createContext(null)
export const OtherContext = React.createContext(null)
const App: React.FC = () = > {
const [state, dispatch] = useReducer(reducer, initState)
const [otherState, otherDispatch] = useReducer(otherReducer, otherInitState)
const valueA = useMemo(() = > [state, dispatch], [state])
const valueB = useMemo(() = > [otherState, otherDispatch], [otherState])
return (
<ThemeContext.Provider value={ valueA} >
<OtherContext.Provider value={ valueB} >
<Toolbar />
<OtherComponent />
</OtherContext.Provider>
</ThemeContext.Provider>)}Copy the code
The complete code
import React, { useReducer, useContext, useMemo } from 'react'
interface IState {
theme: string
}
/** * context */
export const ThemeContext = React.createContext(null)
export const OtherContext = React.createContext(null)
/** * state */
const initState: IState = {
theme: 'dark',}const otherInitState = 'otherState'
/** * reducer */
const reducer = (state, action) = > {
switch (action.type) {
case 'changeTheme':
return {
...state,
theme: action.val,
}
default:
return state
}
}
const otherReducer = (state, action) = > {
switch (action.type) {
case 'changeState': {
return action.val
}
default:
return state
}
}
const ThemedButton = React.memo(() = > {
console.log('button render')
const ctx = useContext(ThemeContext) || {}
const [state = {}, dispatch = null] = ctx
const changeTheme = () = > {
dispatch({
type: 'changeTheme'.val: 'light'})},return (
<>
<h1>{ state.theme }</h1>
<button type="button" onClick={ changeTheme} >
changeTheme
</button>
</>)})const Toolbar = React.memo(() = > <ThemedButton />)
const OtherComponent = React.memo(() = > {
console.log('other component render')
const ctx = useContext(OtherContext) || {}
const [state = ' ', dispatch = null] = ctx
const changeState = () = > {
dispatch({
type: 'changeState'.val: 'change state'})},return (
<>
<h1>{ state }</h1>
<button type="button" onClick={ changeState} >
changeOtherState
</button>
</>)})const App: React.FC = () = > {
const [state, dispatch] = useReducer(reducer, initState)
const [otherState, otherDispatch] = useReducer(otherReducer, otherInitState)
const valueA = useMemo(() = > [state, dispatch], [state])
const valueB = useMemo(() = > [otherState, otherDispatch], [otherState])
return (
<ThemeContext.Provider value={ valueA} >
<OtherContext.Provider value={ valueB} >
<Toolbar />
<OtherComponent />
</OtherContext.Provider>
</ThemeContext.Provider>)}export default App
Copy the code
Use memo in child components
Re-render can be avoided by using useMemo/React. Memo
const OtherComponent = React.memo(() = > {
const ctx = useContext(OtherContext) || {}
const { state = ' ', dispatch = null } = ctx
const changeState = useCallback(() = > {
dispatch({
type: 'changeState'.val: 'change state',
})
}, [dispatch])
return useMemo(() = > {
console.log('other component render')
return (
<>
<h1>{ state }</h1>
<button type="button" onClick={ changeState} >
changeOtherState
</button>
</>
)
}, [changeState, state])
})
Copy the code