前言
- 细阅此文章大概需要 <math xmlns="http://www.w3.org/1998/Math/MathML"> 7 分钟 \color{red}{7分钟} </math>7分钟左右
- 本篇中讲述了:
- 第一步:reducer的拆分和合并
- 第二步:派发行为标识宏管理
- 第三步:actionCreator的创建
- combineReducers源码解析(待补充)
- 欢迎在评论区探讨、留言,如果认为有任何错误都还请您不吝赐教,万分感谢。希望今后能和大家共同学习、进步。
- 下一篇会尽快更新,已经写好的文章也会在今后进行不定期的修订、更新。
- 如果觉得这篇文章对您有帮助,还请点个赞支持一下,谢谢大家!
- 欢迎转载,注明出处即可。
redux工程化简述
在现在的前端开发工作中,一般一个项目都是由一个或多个团队共同维护的,当不同的人同时对不同的模块进行开发时,需要操作的redux部分也肯定会有重叠,不仅需要频繁的处理冲突,而且可能一个公共容器文件就有成百上千行的代码,非常的不利于我们的维护和管理。所以我们在redux的使用上需要进行一些工程化的改造。
第一步:reducer的拆分和合并
首先,将reducer按照模块进行拆分,每个模块都创建自己的reducer。最后把所有的reducer合并为一个整体,传递给store。
创建reducer目录
在store
文件夹下,创建一个reducers
文件夹
- 创建一个
index.js
文件,最终我们将会在该文件中进行不同reducer
的合并。 - 分别创建不同的
xxxReducer.js
文件,用来创建不同模块的reducer
创建不同模块的reducer
javascript
/* moduleAReducer.js */
// 为reducer创建一个初始的状态值
const initState = {
a:0,
b:0
}
/*管理员:修改store中的公共状态*/
export default function moduleAReducer(state=initState, action){
// state: store中的公共状态(最开始没有时,为自己设置的初始值)
// action:dispatch执行时传入的行为对象,行为对象中必须包含type属性(代表派发的行为标识)
// 为了避免在过程中对`state`进行修改,而是需要在`return时`对`state`进行整体替换,所以就需要我们在最开始把获取的state克隆一份
state={...state}
switch(action.type){
// ...根据传递的type不同,修改不同的状态信息
case "adda":
state.a++
break;
case "addb":
state.b++
break;
default:
break;
}
return state // 返回的信息将会替换store容器中所有的公共状态
}
javascript
/* moduleBReducer.js */
// 为reducer创建一个初始的状态值
const initState = {
c:0,
d:0
}
/*管理员:修改store中的公共状态*/
export default function moduleBReducer(state=initState, action){
state={...state}
switch(action.type){
case "addc":
state.c++
break;
case "addd":
state.d++
break;
default:
break;
}
return state
}
合并reducer
可以利用redux当中的combinReducers
方法来合并多个reducer
js
/* 合并各模块的reducer */
import {combinReducers} from 'redux'
import moduleBReducer from './moduleBReducer'
import moduleAeducer from './moduleAeducer'
const reducer = combinReducers({
moduleA: moduleAeducer
moduleB: moduleBReducer
})
export default reducer
将reducer合并之后,公共容器中的公共状态
,也将按照我们设置的成员名字,分模块来进行管理。
js
/* 合并后的公共状态如下 */
state={
moduleA={
a:0,
b:0
},
moduleB={
c:0,
d:0
}
}
在combinReducers
设置什么模块名,合并后公共容器
就会以模块名
来分别管理各自模块的公共状态
,类似作用域,即使不同模块的状态名重复,也并不会冲突。
在使用不同模块的状态时,可以使用对象成员访问的方式进行
js
store.getState().moduleA
store.getState().moduleB
// ....
引入到公共容器中
javascript
// 创建公共容器
import {createStore} from 'redux'
import reducer from './reducers'
/*创建公共容器*/
const store = createStore(reducer)
export default store
在后代组件中的使用变化
获取不同模块的状态时,可以使用对象成员访问的方式进行
js
store.getState().moduleA
store.getState().moduleB
// ....
派发任务时的流程变化
派发的操作没有变化,只是执行dispatch
时,会去reducer
当中按照代码顺序,逐一从每个模块的case
当中匹配行为标识
,若当前模块没有,就会去下个模块接着找,直到找到对应的行为标识的case
,并执行相应的操作。
第二步:派发行为标识宏管理
背景
在各模块的reducer合并后,当执行dispatch
时,会去reducer
当中逐一从每个模块的case
当中匹配行为标识
,这个时候可能会出现一种情况,那就是两个不同模块中,具有两个相同名称的行为标识。虽然名称相同,但是执行对应的操作,可能会导致出现不期望出现的更新。
统一管理行为标识
所以不管是哪个模块,我们派发的行为标识,一定要保证其唯一性。
创建action-types文件
在store目录
下,创建action-types.js
文件,在此文件中,对所有需要派发的行为标识进行统一管理。
js
/* 统一管理需要派发的行为标识 */
// 为了保证不冲突,需要有个统一的命名规范,如:模块名_派发的行为标识(整体大写)、
// 使用谓语:变量的存储的值是一致的
// 所有需要派发的行为标识,都放在这里定义
export const moduleA_ADDA = moduleA_ADDA
export const moduleA_ADDB = moduleA_ADDB
export const moduleB_ADDC = moduleA_ADDC
export const moduleB_ADDD = moduleA_ADDD
// .....
在不同模块的reducer中使用
从action-types.js
文件中引入所有的行为标识,然后进行使用。由于是引用使用,还能更好的避免拼写错误
javascript
/* moduleBReducer.js */
import * as TYPES from '../action-types'
// 为reducer创建一个初始的状态值
const initState = {
c:0,
d:0
}
/*管理员:修改store中的公共状态*/
export default function moduleBReducer(state=initState, action){
state={...state}
switch(action.type){
case TYPES.moduleB_ADDC: // 使用同一管理的宏标识,不再使用自己写的字符串了
state.c++
break;
case TYPES.moduleB_ADDD: // 使用同一管理的宏标识,不再使用自己写的字符串了
state.d++
break;
default:
break;
}
return state
}
在后代组件中派发使用
js
/*引入上下文*/
import AppContext from './AppContext';
import * as TYPES from '../action-types'
const moduleB = function () {
const {store} = useContext(AppContext)
const handleAddC = () => {
store.dispatch({type:TYPES.moduleB_ADDC})// 使用同一管理的宏标识,不再使用自己写的字符串了
}
const handleAddD = () => {
store.dispatch({type:TYPES.moduleB_ADDD})// 使用同一管理的宏标识,不再使用自己写的字符串了
}
return <div>
<button onclick={handleAddC}>add C</button>
<button onclick={handleAddD}>add D</button>
</div>;
};
export default moduleB
第三步:actionCreator的创建
将各模块中派发的行为对象进行统一管理
把派发的行为对象,按照模块进行统一管理。【就是指执行dispatch时,传递进去的那个对象】
创建actions文件
在store目录
下,创建actions
文件夹,并创建index.js
文件,在此文件中,我们会将所有的action(行为对象)合并到一起。
创建各个模块的行为对象文件
在store目录
下的actions
文件夹中创建各个模块的action
文件,该文件会导出一个对象,对象当中存储的是若干方法,每一个方法执行返回的就是该模块中的某个需要派发的行为对象。该文件涵盖了该模块所有需要派发的行为对象
js
/* moduleAAction.js */
import * as TYPES from '../action-types'
const moduleAAction = {
adda(){ return {type:TYPES.moduleA_ADDA} }
addb(){ return {type:TYPES.moduleA_ADDB} }
}
export default moduleAAction
js
/* moduleBAction.js */
import * as TYPES from '../action-types'
const moduleBAction = {
addc(){ return {type:TYPES.moduleB_ADDC} }
addd(){ return {type:TYPES.moduleB_ADDD} }
}
export default moduleBAction
合并为一个action
js
/* 合并各模块的action */
import moduleAAction from './moduleAAction'
import moduleBAction from './moduleBAction'
const action = {
moduleA: moduleAAction
moduleB: moduleBAction
}
export default action
js
/* 合并后的action如:*/
action={
moduleA:{
adda(){ return {type:TYPES.moduleA_ADDA} }
addb(){ return {type:TYPES.moduleA_ADDB} }
},
moduleB:{
addc(){ return {type:TYPES.moduleB_ADDC} }
addd(){ return {type:TYPES.moduleB_ADDD} }
},
}
在后代组件中派发
js
/*引入上下文*/
import AppContext from './AppContext';
import action from '../store/actions'
const moduleB = function () {
const {store} = useContext(AppContext)
const handleAddC = () => {
// store.dispatch({type:TYPES.moduleB_ADDC})// 使用同一管理的宏标识,不再使用自己写的字符串了
store.dispatch(action.moduleB.addc) // 直接使用合并后的action中统一管理的行为派发对象,和上面是一致的
}
const handleAddD = () => {
// store.dispatch({type:TYPES.moduleB_ADDD})// 使用同一管理的宏标识,不再使用自己写的字符串了
store.dispatch(action.moduleB.addd)// 直接使用合并后的action中统一管理的行为派发对象,和上面是一致的
}
return <div>
<button onclick={handleAddC}>add C</button>
<button onclick={handleAddD}>add D</button>
</div>;
};
export default moduleB
此操作被称为actionCreator
,在目前看来虽然仍是套娃,而且更繁琐,但是在处理react-redux时,会非常有用。