前言
userReducer可以帮助我们集中式的处理复杂的state管理。但如果我们的页面是多层组件组成,我们如何在子组件中触发state变化呢?此时useContext就上场了。
例子
此时我们需要做如下几件事情:
- 创建
globalState.jsx文件用来存放全局state,并初始化provider - 在
app.jsx文件,也就是入口文件处引入provider - 在子组件中使用
useContext
globalState.jsx文件
// globalState.jsx
// 创建默认状态值,store
const initState = {
title: '',
isShow: false,
}
// 定义action,作为dispatch的参数
const actionType = {
SET_TITLE: 'SET_TITLE',
SET_SHOW: 'SET_SHOW',
}
// 处理dispatch推送过来的数据
function reducer (state, action) {
switch (action.type) {
case actionType.SET_TITLE:
return { ...state, title: action.payload }
case actionType.SET_SHOW:
return { ...state, isShow: true }
default:
reutrn state
}
}
// 创建context全局上下文
const GlobalContext = createContext({}) // 注:使用ts写时createContext需要传1到2个参数,可用{}代替,否则会报错
或
const GlobalContext = createContext({ state: initState, dispatch(action) => {} })
// 定义provider
function GlobalProvider(props) {
// 使用useReducer来初始化全局state
const [state, dispatch] = useReducer(reducer, initState)
return (
<GlobalContext.Provider value={{ state, dispatch }}>
{props.children} // 接收传入的值
</GlobalContext.Provider>
)
}
// 将context相关的要用到的内容导出
export {
GlobalProvider,
GlobalContext,
initState,
actionType,
reducer,
}
在ts中createContext的写法:
参考文档: stackoverflow.com/questions/5…
interface IContextProps {
state: IState;
dispatch: ({type}:{type:string}) => void;
}
const AdminStore = React.createContext({} as IContextProps);
或
// ts写法
interface StoreAction {
type: string
payload?: any
}
const GlobalContext = createContext({
state: defaultData,
dispatch: (action: StoreAction) => {},
})
// 并关闭 .eslintrc 规则
rules: {
'@typescript-eslint/no-empty-function': 'off',
},
app.jsx入口文件
// 引入Store
import { GlobalProvider } from 'xxxxxx'
export function Root() {
return (
<GlobalProvider>
// 子组件
<ChildComponent />
</GlobalProvider>
)
}
在ChildComponent中使用useContext
// ChildComponent.tsx
import { actionType, GlobalContext } from 'xxxxxx'
function ChildComponent() {
const { state, dispatch } = useContext(GlobalContext)
useEffect(() => {
dispatch({ type: actionType.SET_SHOW })
},[])
return (
{ state.isShow ? <div>{state.name}<div> : <Fragment /> }
)
}
从上述例子中可以看出用useReducer+useContext,可以通过context把dispatch函数提供给组件树中的所有组件使用,而不用通过props的方式一层层传递。
使用Context比回调函数的优势:
- 对比回调函数的自定义命名,
Context的API更加明确,可以清晰的知道哪些组件使用了dispatch、应用中的数据流动和变化。这也是React单向数据流的优势。 - 更好的性能。如果使用回调函数作为参数传递的话,因为每次
render函数都会变化,也会导致子组件的rerender。当然,我们也可以使用useCallback来解决这个问题,但相比useCallback,React官方更推荐使用useReducer,因为React会保证dispatch始终是不变的,不会引起consumer组件的rerender。
总结:
- 如果页面的
state很简单,建议直接使用useState。 - 如果页面
state比较复杂(state是一个对象或者state非常多散落在各处),建议使用useReducer。 - 如果页面的组件层级比较深,且子组件需要出发
state的变化,可以考虑useReducer+useContext相结合的方式来使用。