前言
在一些中小型的项目中,我们也不可避免的会有需要做状态管理的需求,但是引入
redux好像又有点杀鸡焉用牛刀的感觉,所以这种情况下我们就可以用react的Context来手动实现一个简单的redux(使用react hooks)
什么是Context
react的数据流是一个自顶向下的单向数据流,通过props逐级往下传递数据,当层级变得复杂的时候,传递props就会变得极其繁琐,而react则给出了一种不需要手动添加props就可以在组件间共享数据的能力,这就是Context。
一、创建一个Context对象
先在入口文件里创建一个Context对象,然后我们需要把这个Context对象套在最外层组件的外面,并且要给他一些默认值,这里我们使用react hooks提供的useReducer来生成默认值。
//index.js
export const IndexContext = createContext();
const Main = () => {
const [state, dispatch] = useReducer(reducers, init)
return (
<IndexContext.Provider value={{state, dispatch}}>
<Router></Router>
</IndexContext.Provider>
)
}
这里的reducers是项目中用到的所有的reducer方法的集合,init则是Context的默认值,这里提到的reducer与我们在redux中使用的reducer概念是相同的,我们把我们的reducer和默认值作为参数传进useReducer函数会得到一个返回数组,数组第一项是Context对象的值,第二项为触发reducer的dispatch函数。另外我们平时写redux时会按组件对reducer进行文件区分,最终通过combinReducer再合为一个进行导出,这样方便开发,同样我们也可以自己实现一个combinReducer。
二、combinReducer与reducer文件结构
//userReducer.js
const init = {
userId: "",
token: "",
userName: "",
phone: "",
openid: ""
};
export const userReducer = (state = init, action) => {
switch (action.type) {
case "login": {
return {
...state,
...action.payload
};
}
case "logout": {
return {
...state,
userId: "",
token: "",
userName: "",
phone: "",
openid: ""
};
}
default:
return state;
}
};
//这是某一个reducer的文件,要实现combinReducer实际上就是把所有文件的init对象合并为一个对象,某一个action产生时遍历触发所有文件中对该action的reducer,实现如下
//index.js
const combinReducer = reducer => {
const reducerKeys = Object.keys(reducer)
let initStateObj = {}
reducerKeys.forEach(key=>{
const initState = reducer[key](undefined, {type: ""})
if(!initState){
throw new Error(`${key}没有返回state`)
}else{
initStateObj[key] = initState
}
})
return (state, action) => {
if(action){
reducerKeys.forEach(key=>{
const prevState = initStateObj[key]
initStateObj[key] = reducer[key](prevState, action)
})
}
return {...initStateObj}
}
}
export const reducers = combinReducer({userReducer, chooseReducer, myReducer})
三、在组件中使用
有了以上步骤之后,我们在组件中通过useContext就可以拿到订阅的值和dispatch方法
// *.jsx
const { state, dispatch } = useContext(IndexContext);
const XX = () => {
useEffect(()=>{
request("xxxx").then(data => {
dispatch({
type: "setData",
data
})
})
})
return (
<Fragment>
state.xxreducer.list.map(item=>{
return <div></div>
})
</Fragment>
)
}
//在业务组件中诸如此类使用即可
后记
可以看到使用react hooks实现的状态管理,在语法上使用习惯上十分接近redux,同时又十分灵活小巧,比较适合中小型项目,没有那么多状态需要管理的情况,像我们简单的用户登录状态的全局保持,或者是列表页返回需要停在原来位置,不重新刷新页面之类的简单需求,就不用再引入整个redux,自己动手实现即可。