鼠鼠初学前端时,就很喜欢从一个知识点,联想到在工作时怎么进行使用,比如刚开始学redux,context时便就很想知道这俩个的产生是为了管理React项目状态的,究竟企业级项目是有多少状态要管理,才需要这个。这几天鼠鼠实习研究公司项目时才发现....... 这数据也太多了吧!!!
前言
本文将会从Redux和Context的基本使用开始介绍,再带大家学习一下在企业项目里如何使用他们来管理状态。通过本文,大家可以学习到:
1.redux和context的基本使用
2.redux怎么管理大型项目
3.context怎么管理大型项目
4.我们该如何利用这些知识,来管理自己项目的状态
Context的基本使用
为什么我们需要Context
大家都知道,假设兄弟组件与兄弟组件之间有状态共同维持俩个组件的状态,那我们怎么去设置这个状态呢? 肯定最简单的办法就是将状态提升到他们的共同父亲组件上:
我们将状态提升到它们的父组件当中,再通过props下发给子组件。
但是这样管理状态就会有很大的问题,如果这个状态是“表堂兄弟”要用呢?继续看图
倘若我们需要同一个状态的组件“亲属关系”远,我们想要管理这个状态,不仅需要精准的找到它们的共同祖先组件,将状态放入其中,还需要层层props传递到相应的位置,这就会造成俩个很大的问题:
1.如果项目中有bug是这个状态导致的,那么在大型项目里,想要维护将会非常困难,很难定位到这个状态放在哪个组件管理
2.层层传递的props将会非常臃肿。
所以我们针对为了解决这俩个问题,就必须有这样一个状态管理方式,能够实现
- 可以不需要重重传递props,而是做到哪个组件要用直接就能提取
- 需要的状态放入一起管理,便于以后维护。
这也是Context给我们带来的好处。
我们如何使用Context去管理简单的项目状态
我们先来复习一下context的基本使用方法:
1.创建Context
import React from 'react';
const MyContext = React.createContext(defaultValue);
defaultvalue是没有provider的值
2. 使用 Provider 提供数据
<MyContext.Provider value={传递的值}>
<App />
</MyContext.Provider>
我们将创建的Context以provider的形式套在最外层的组件上,这样子该组件及其所有子孙组件都能够使用provider传递的值
3.在子组件中使用context
import React, { useContext } from 'react';
const value = useContext(MyContext);
通过useContext这个API就可以马上获取我们context存储并传递的数据。
接下来我们总结一下思路,如何简单使用Context来管理我们想要的状态:
1.确定我们需要全局管理的状态
2.将我们需要全局管理的状态创建一个provider,并包裹在最高组件并以value传进去
3.被包裹的子孙组件如果想用就可以使用useContext这个API快速获取状态
这样我们就不需要再层层去传递props,导致其十分臃肿,当我们组件由于某个状态出现问题时,可以通过provider,迅速找到该状态。
在企业级大型项目该如何使用Context?
在谈在大型项目如何使用前,我们不妨先思考一下大型项目与我们平时拿来练手的项目有哪些不同,根据这些不同, 我们才能够更好的去思索如何去使用Context。
-
状态庞大,且种类繁多:比如一些页面,没氪金用户是一个页面,但是到了vip用户就是另一个页面。这个状态就属于用户状态的一种。又比如,用户是什么设备点开的该网页,手机?window?mac?或者是我们三折叠?我们的页面可能会由于用户设备的问题去调整页面样式,这就是属于设置方面的状态。
-
更改状态方法多:拿之前的判别vip用户状态举例,我在充值页面充了VIP,就需要写一个方法去改变这个状态,可能我看10秒广告,给我vip,可能又要写一个方法去改变状态。
针对这俩个不同点,企业项目使用Context管理状态的特点就呼之欲出了
1.需要将不同的状态以相同的特点分好类,
2.需要将处理这些状态的方法统一起来,并跟着分好类,实现我们只写一次方法即可使用,且易于维护
接下来就是针对这些特点去设计一个Context管理状态的
1.状态分类
其实Context的状态分类很好理解,就是以不同的类型去创建出不同的provider,再将其嵌套在app组件中。 比如说,我们需要分类出两种种类的状态,一种是用户类状态,一种是判别用户设备类状态 我们就得先创建Context,并设置好provider:
用户类状态
export const defaultUserState: UserState = {
fetchUserComplete: false,//用户信息是否加载完毕
otherInfo: {
isLogin: false,//是否为登录状态
isVip: false,//是否是vip
},
};
export const UserStateContext = createContext(defaultUserState);
export function UserProvider({ children }: { children: React.ReactNode }) {
return <UserStateContext.Provider>{children}</UserStateContext.Provider>;
}
设备状态
type DeviceType = 'pc' | 'mobile';
//初始值为pc
const DeviceContext = createContext<DeviceType>('pc');
function DeviceProvider({ children }: { children: ReactNode }) {
//获取用户设备宽度
const size = useSize(() => document.querySelector('body'));
const thresholdWidth = 750;
return (
//判断不严谨,为了好理解
<DeviceContext.Provider value={size?.width <= thresholdWidth ? 'mobile' : 'pc'}>
{children}
</DeviceContext.Provider>
);
}
export { DeviceContext, DeviceProvider };
我们创建好按照状态分类好创建provider后,就得开始嵌套了
<UserProvider>
<ConfigProvider locale={zhCN}>
<DeviceProvider>
<App {...pageProps} />
</DeviceProvider>
</ConfigProvider>
</UserProvider>
之后我们只要是app组件下的组件,都可以使用我们的保存好的状态了,而且这种分类以便于我们去寻找对应的状态,使得我们维护起来更加方便,
2.分类创建改变状态的方法
我们沿用之前的思路,我们可以将变量通过context传到下面组件去,是否也能够将函数也通过这种方式传递下去呢?
当然也是可以的,所以我们的思路就是,将方法和状态存入一个store里面去,再通过provider的value传过去。并且这个方法也是需要分类的,专门处理user的方法就放任user provider里面去这样的。 我们还是拿user举例子
export function useUserStore() {
const [userState, setUserState] = useState<UserState>(defaultUserState);
getUser = () => {
//改变,获取用户状态
setUserState(...)
}
return {
...userState,
getUser,
};
export const UserStateContext = createContext<UserState>(defaultUserState);
export function UserProvider({ children }: { children: React.ReactNode }) {
const store = useUserStore();
return <UserStateContext.Provider value={store}>{children}</UserStateContext.Provider>;
}
我们将状态交由这个自定义hook去管理,并且将用户的状态以及改变状态的方法以value的形式传递下去,这样做,我们能够实现同一分类下的状态统一交由该类别的自定义hook的store进行管理,自定义hook也会将改变状态的方法以及状态以context的value形式传递下去。
总结一下:
为什么我们需要分类管理?
因为企业项目状态过多,需要分类,方便维护。
我们该如何去做
将不同的状态进行分类,同一类的状态创建一个 provider 并以其他的provider在最高组件上进行嵌套
为什么我们需要将改变状态的方法分类并且统一管理
如果改变状态的方法随意创建容易造成状态污染,且难以维护
该如何去做
将分类的状态交由一个自定义hook,在这个自定义hook里进行统一的状态存储和状态更改,并将更改状态的方法和状态以value的形式传递给其他组件
Redux的基本使用
相信大家在学习React的时候,也是学习过如何Redux,这里就对Redux进行简单的介绍。
Action 是一个普通的 JavaScript 对象,用来描述“发生了什么事”。
const action = {
type: 'INCREMENT',
payload: 1 // 可选字段,带上具体数据
};
把 Action 想象成一张订单,你告诉 Redux:“我想做某件事(比如加1)”。
Reducer 是一个纯函数,接收旧的 state 和 action,返回一个新的 state。
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
Reducer 像是厨师,你给他菜单(Action)和材料(旧 state),他做出新菜(新 state)
Store 是一个状态容器,用来统一管理整个应用的状态。
由 Redux 提供的 createStore(reducer) 方法创建。
当我们想要改变store时,就需要dispath一个action交给reducer处理
store.dispatch({ type: 'INCREMENT' });
在企业级大型项目该如何使用Redux
和context类似,面对数量庞大的状态要进行管理,就必须得做好分类,但是我们需要根据Redux的特性去进行针对化改动
1.如何将reducer分类处理,并将多种reducer合成一个总的大reducer交由creatStore去创建
2.与Context不同,如果我们异步改变状态,在Context可以自定义函数去写,而dispath不允许异步处理,我们需要有办法可以去实现异步dispath方法
分类reducer
在这里redux自带了一个API能够将我们创建的很多小reducer合成一个总reducer combineReducers
import { combineReducers } from 'redux';
// 分别管理 user 和 counter 的 reducer
function userReducer(state = {}, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, ...action.payload };
default:
return state;
}
}
function counterReducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
}
// 使用 combineReducers 合并 reducer
const rootReducer = combineReducers({
user: userReducer,
counter: counterReducer,
});
这里是为了读者方便都写在了一起,而在实际开发中,我们不同的小reducer也有着不小的代码量,我们就需要分割成不同的文件对这些的reducer进行维护。
之后我们在将合成的rootReuder交给creatStore去创建仓库即可。
如何异步dispath改变状态
在redux中,我们需要一个中间件thunk,允许我们 dispatch 一个函数,而不是只能 dispatch 一个普通对象。这样你就可以写异步逻辑
然后还需要将这个中间件安装到Redux 的 dispatch 流程中
Redux 的中间件(middleware)是啥?
中间件是 Redux 的扩展机制,它可以“拦截” dispatch(action) 的过程,用于:
处理异步请求(如:redux-thunk, redux-saga)
打日志(如:redux-logger)
做权限判断、数据清洗等
我先把处理后的中间件给大家看看
const middlewareEnhancer = applyMiddleware(thunkMiddleware);
applyMiddleware(...)
Redux 提供的一个高阶函数,用来把中间件“安装”到 Redux 的 dispatch 流程中。
thunkMiddleware
就是你从 redux-thunk 导入的中间件,作用是允许你 dispatch 一个函数,而不是只能 dispatch 一个普通对象(Action)。这样你就可以写异步逻辑:
dispatch((dispatch, getState) => {
fetch('/api/data').then(res => res.json()).then(data => {
dispatch({ type: 'SET_DATA', payload: data });
});
});
之后我们就可以将这个中间件以及我们之前创建的rootRducer一同交给creatSore
const store = createStore(rootReducer, middlewareEnhancer);
如此,我们就做到了使用Redux进行状态分类管理,还可以使用异步方法改变我们的状态
总结一下:
我们该如何使用redux进行状态分类?
根据不同类型,创建出不同类的reducer并使用combineReducers进行合并
dispath不能异步改变状态,遇到想要异步时,该怎么办?
我们使用redux的中间件机制,就可以在dispath中传一个函数去实现异步操作。
后言
redux现在在大型项目用的比较少了,鼠鼠也是在比较老的项目发现在使用的,因为它本身就比较复杂,不太好用。所以新项目基本上弃用了。但是为什么鼠鼠要在这介绍呢,因为redux算是React状态管理的基础了。打好基础才是最重要的
更文不易,卑微求赞。