useReducer
useReducer 通常被用于替代useState来统一管理状态,避免过多的state带来的开发不便
示例
useReducer接收两个参数: reducer函数和初始状态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={'姓名'} onInput={(e)=>handleInput(e.currentTarget.value,'name')}/>
<input placeholder={'年龄'} onInput={(e)=>handleInput(e.currentTarget.value,'age')}/>
</div>
)
}
useContext
在组件嵌套深度较大时,属性透传变得很麻烦, useContext可以让状态传递简单很多
// 第一步:创建需要共享的context
export const ThemeContext = React.createContext('light');
const App = ()=>{
const [value,setValue] = useState('dark')
useEffect(()=>{
setTimeout(()=>setValue('blue'),2000)
},[])
// 第二步:使用 Provider 提供 ThemeContext 的值,Provider所包含的子树都可以直接访问ThemeContext的值
return (
<ThemeContext.Provider value={value}>
<Toolbar />
</ThemeContext.Provider>
);
}
// Toolbar 组件并不需要透传 ThemeContext
function Toolbar(props) {
return <ThemedButton />;
}
function ThemedButton(props) {
// 第三步:使用共享 Context
const theme = useContext(ThemeContext)
return <h1>{theme}</h1>
}
export default App
useReducer + useContext
useReducer可以管理状态,useContext可以传递状态,那么相结合其实就可以作为一个小型的状态管理器了。
以上述的useContext为基础,改造一下, 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
useContext渲染问题
被Context.Provider包裹的所有子组件里,只要调用了useContext,那么在context发生变化时就必然会re-render,这会带来很多不必要的渲染开销,目前也有一些解决方案
拆分context
即不要将所有state都放到一个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>
)
}
这时候可能发现依然会有re-render问题,简单地说,OtherComponent虽然只订阅了OtherContext,但每次传下来的value都是新的数组引用,那么改造一下,用memo来缓存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>
)
}
完整代码
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
子组件中使用memo
可以通过useMemo / React.memo 来避免re-render
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])
})