Redux Thunk以及简易middleware构建

  咱之前有记录过关于redux的东西。然而就算加上了redux,react依然不能作为一个完整框架使用(其实人家本就只算是个lib…)是残缺的(对于这点我只能说,还是angular方便- -)。非常需要解决的有两点。
  1、SPA得要有前端路由..。
  2、网络请求放在哪里?(这一点其实官网上面有提过componentDidMount是发送网络请求的好地方原话:this is a good place to instantiate the network request.,然而在componentDidMount里面网络请求一点都不好用..原因是在componentDidMount里面延迟setState会有警告具体参考另外一篇记录,而且网络请求堆在component里面也是非常的不方便,最好能够将网络请求统一管理起来…..)

1 thunk

  先来回顾一下redux流程..
redux.png
  在这里咱就发现了,redux有点问题,它只能dispatch一个包含了更新state数据的纯object,它没有办法再dispatch里面处理一些异步逻辑。最简单来讲我希望我发出网络请求的时候有个state更新,在请求失败的时候有个state更新,在请求成功之后有个state更新。
  redux-thunk解决的就是这么一个问题,redux-thunk作为redux的中间件,在action执行之前,会判断传入的action是不是function如果是则将dispatch getState传入该function并执行。代码就几行来着..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);
};
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

  所以有了thunk之后我们写异步请求的时候就是这样写的..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// eslint-disable-next-line no-unused-vars
export const fetchUsers = () => {
const url = USERS_URL;
return async (dispatch) => {
dispatch({ type: USERS_REQUEST });
try {
const response = await apiClient(url, 'get');
const data = await response.json();
dispatch({ type: USERS_RECEIVE, data });
} catch (error) {
const data = [{
name: 'mock name',
id: 'mock id',
desc: 'mock desc',
}];
dispatch({ type: USERS_RECEIVE, data });
}
};
};

  这里这个fetchUsers返回的是一个function,然后thunk将其拦截下来,将dispatch传入并执行这个function。
  网络请求的问题解决了,那单页面应用的路由问题咋整呢?我自己在网上找的时候一片片的全是react-router。然而react-router所有的路由跳转都自己包装了一遍,我需要从react-router-dom里面引用组件来跳转。如果我想在redux的action里面进行路由跳转…又是只能挠挠头了….

2 middleWare的应用

  到了项目上面发现居然用的不是这个…咱可以不用react-router-dom,所有的路由跳转都是一句话history.push()。说实话这个看上去比react-router好使多了,咱终于是能在redux里面跳转路由了。
  那么这东西咋用呢,其实这个在react-router的doc里面有提及过…在https://reacttraining.com/react-router/web/guides/redux-integration的东西(翻墙体验更加),也没啥好说的看看文档就会了。主要还是想说一下项目里面别人搞得一个middleWare。
  是这样的,现在做的东西虽然流程上面有点复杂,但是总体上来说是类似于银行自助服务机上面的那样的东西,就是走流程性的东西。本来以我自己的做法的话可能就是在页面上面的next button里面判断一下条件决定后面应该进入哪个页面,然后再调用history push去跳转页面。
  但是这样会有个问题大量的判断逻辑堆在了component里面。会显得组件非常的臃肿,代码可能就长成这样

1
2
3
4
5
6
7
8
9
handleNext(){
if(conditionA(props.reducer.data)){
history.push('/aa');
}
else if(conditionB(props.reducer.data)){
history.push('/BB');
}
// and so on
}

  但是我也不知道该咋改啊….然后我没纠结多久,有人写了个middleWare去解决(虽然后面这个东西越写越臃肿..但是思路确实是学到了)…
  首先,我们先收集到所有情况的路由跳转。然后根据这个建立一个url跳转的map。类似于这样的object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const urlMaps = [
{
currentUrl:'/home',
nextUrls:
[
{
url:'/step1',
condition:(state)=>{}
},
{
url:'/b',
condition:(state)=>{}
},
],
enterLog:(state)=>{}
},
{
currentUrl:'/step1',
nextUrls:
[
{
url:'/a',
condition:(state)=>{}
},
{
url:'/b',
condition:(state)=>{}
},
],
enterLog:(state)=>{}
}
]

  然后再暴露出一个专门控制路由的方法来。根据当前的路由(由于用的是connected-react-router,reducer里面有保存当前页面的路由的,在 state.router.location.pathname里面。

W}8FL7OYM7DZPT8}727X@G4.png

获得urlMap,然后根据urlMap中的condition来决定下一个页面的路由。
  路由控制是给它整一块儿了,顺手把一些进入页面就需要干的事情给他干了,比如我每个页面的每一次进入都需要向后台发一次请求,记录页面的进入(后台需要统计每个页面的进入次数用于分析),其实也类似于打log的存在了。
  这个时候咱整一个middleWare来拦截路由变化(connected-react-router每次路由变化的时候都会dispatch一个type为Location_change的action,拦截这个action即可)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const logMiddleWare = store => next => action => {
switch (action.type) {
case LOCATION_CHANGE:
const currentUrl = getMapItem(
urlMaps ,
action.payload.location.pathname//这个payload就是要更改的数据
);
if (currentUrl) {
store.dispatch(enterLog(currentUrl, store.getState()));
}
break;
}
return next(action);
};

  emmm项目上用到的redux基本上就这样了…该记录的都记录下来了。其实一开始思路想到建立一个urlMap的时候会想到一旦页面多起来了咋整。但是现在的项目是这样的,每一个flow都是一个单独的SPA,所有的状态都是单独管理互不干扰的,就算我有好几页,一页六七个flow,我也只需要专心眼前的flow就完事儿了。