Redux - rest middleware

Redux 是一个状态管理器,常用作 React 的状态管理。
React 结合使用 Redux 非常棒,没有了解过 Redux 的同学,可以戳官网了解一下。
今天笔者分享一款自己写的 Redux 中间件, 它被用来处理所有的 rest 接口请求,统一管理 ajax 状态和返回的数据。
闲话不多讲,项目地址:https://github.com/HelloYie/redux-rest-middleware

背景

近期公司有一个内部项目,是一个复杂的管理系统。按照国际惯例,分析完需求就得分析分析技术选型。讨论后决定 web 前端端采用 React + Redux + Webpack + Babel 的模式。
后端采用 Django restframework。前后端分离解耦,采用 rest 规范对接接口。同时后端也可以很方便的给 Android\IOS 开发的同学提供接口。感觉还行!

redux-rest-middleware

由于采用 rest 规范,通过浏览器自带的 fetch 请求接口。
请求被分为了 GET POST PUT PATCH DELETE。这里简单介绍一下这几个 method 的使用场景。

  • GET 读取数据,不作更新,是安全的。如:/api/order/list /api/order/1 等。
  • POST 新增一条数据,往插入一条新数据。
  • PUT 更新整条记录。如原始记录是 { id: 1, name: '张三', age: 11 }, 做更新时 /api/order/1 {name: '李四', age: 11 },需要把整条记录发给接口。
  • PATCH 更新记录的部分信息,如更新 { name: '李四' },只需要传需要更新的信息,我比较常用。
  • DELETE 从数据库删除一条记录。

虽然请求各有不同,但是每个请求都会经历 [发起请求 pending] ==> [ 请求成功 success, 返回数据和200] 或者 [请求失败 error, 返回错误状态码],
基于这点,我们可以给我们的 redux 加一个 middleware 来统一处理 http 请求,具体思路如下:

  • Q_PENDING: 请求开始时为 state.api.XXXModule 置一个 { isFetching: true } 的状态。
  • Q_SUCCESS: 请求成功后为 state.api.XXXModule 置一个 { isFetching: false, response: XXX } 的状态。
  • Q_ERROR: 请求失败(请求超时、400、401、403、404、500)为 state.api.XXXModule 置一个 { isFetching: False, response: errorFromServer } 的状态,
    捕获到接口返回的错误代码和信息,做相应操作。

redux-devtool 截图

  • Q_SUCCESS

redux-devtool-1

  • Q_ERROR

redux-devtool-2

代码

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import cookie from 'js-cookie';
import { replace } from 'react-router-redux';
import { sAlert } from 'utils/sAlert';
import {
Q_PENDING,
Q_SUCCESS,
Q_ERROR,
LOGOUT,
LOADING_BAR,
} from './constants';

export default function inspectorMiddleWare({ dispatch, getState }) {
return next => action => {
const { module, promise, payload, onSuccess, onError } = action;
if (!promise) {
return next(action);
}
next({
...payload,
type: Q_PENDING,
module,
});

next({
type: LOADING_BAR,
loadingBarPercent: 0,
});

const checkLoadingBarPercent = () => {
const sTime = setTimeout(() => {
let loadingBarPercent = getState().ui.loadingBarPercent;
loadingBarPercent += Math.floor(Math.random() * 20);
if (loadingBarPercent < 95) {
next({
type: LOADING_BAR,
loadingBarPercent,
});
checkLoadingBarPercent();
} else {
clearTimeout(sTime);
}
}, 300);
};
checkLoadingBarPercent();

// 异步 action 都将返回 promise
return new Promise((resolve, reject) => {
return promise()
// check http status
.then((response) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
if (response.status === 403) {
sAlert('您没有执行该操作的权限', 'error');
}
if (response.status === 401) {
const { routing } = getState();
const { locationBeforeTransitions } = routing;
dispatch({
type: LOGOUT,
});
cookie.remove('token');
dispatch(replace(`/login`));
}
return response.json().then((resJson) => {
if (reresponse: errJson,
module,
});
} else {
throw errJson;
}
next({
type: LOADING_BAR,
loadingBarPercent: 100,
});
if (onError) {
onError(errJson, dispatch, getState);
}
reject(errJson);
});
});
};
}

使用

在 store 里面添加 middware,如我的middleware 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import thunk from 'redux-thunk';
import { applyMiddleware, compose, createStore } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import { DEBUG } from 'config';
import rootReducer from 'redux/reducers';
import inspectorMiddleware from './middleware';


export default function configureStore(initialState = {}, history) {
// Add so dispatched route actions to the history
const reduxRouterMiddleware = routerMiddleware(history);

const middleware = applyMiddleware(inspectorMiddleware, thunk, reduxRouterMiddleware);

const createStoreWithMiddleware = compose(
middleware,
DEBUG &&
typeof window === 'object' &&
typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
);
const store = createStoreWithMiddleware(createStore)(rootReducer, initialState);
return store;
}

这里我又使用了另外一个middleware,https://github.com/gaearon/redux-devtools,
是 chrome 的 redux 调试器,个人认为是使用 redux 必须的。
redux-devtool

新增一个 reducer 来处理接口请求

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
33
34
35
36
37
38
// api.js

import {
Q_PENDING,
Q_SUCCESS,
Q_ERROR,
} from '../constants';

export default function reducer(state = {}, action) {
const { AuthVerifyPOST, NotificationGET, NotificationPATCH } = state;
switch (action.type) {
case Q_PENDING:
return {
...state,
[action.module]: { isFetching: true, ...action },
};
case Q_SUCCESS:
return {
...state,
[action.module]: {
isFetching: false,
isError: false,
...action,
},
};
case Q_ERROR:
return {
...state,
[action.module]: {
isFetching: false,
isError: true,
...action,
},
};
default:
return state;
}
}

在全局 reducer 中使用该 reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { combineReducers } from 'redux';
import { routerReducer as routing } from 'react-router-redux';
import { reducer as form } from 'redux-form';
import api from './module/api';
import auth from './module/auth';
import ui from './module/ui';
import upload from './module/upload';

export default combineReducers({
ui,
routing,
form,
api, // 这里就是我们处理 api 的reducer 啦。
auth,
upload,
});

尾声

好了,不知道这款 redux-rest-middleware 有没有帮到你呢。
如果感兴趣的话可以戳 https://github.com/HelloYie/redux-rest-middleware 一起来完善或者提出宝贵的意见。