React项目小结系列:项目中redux异步流的选择

本文将探讨Redux中各种异步流的处理及本次项目的选择和实践。

Redux核心理念很简单,单一数据源,只读的state以及纯函数修改state。它的主要流程就是:oldStore->action(from view)->reducer->newState->view。Redux有一个全局的store来保存整个应用的state,我们修改store的唯一方式就是通过dispatch(action),dispatch函数内部会调用reducer返回一个全新的state来更新我们的store。当store发生更新,view就会触发render函数进行更新。

不过Redux本身只能处理同步事件,Redux作者将异步流的处理通过提供中间件的方式让开发者自行选择。

直接在组件内部里处理

最简单就是直接在React组件的内部去处理,通过在组件当中声明异步流函数或者在组件的componentDidMount处理初始化异步事件,然后在异步事件内部使用回调或者promise等异步处理方法,去调用dispacth(action)。这种方法可以直接上手,不过这么做把view和action都混合在一起,代码耦合程度太高,逻辑混乱,后期也难以维护。

redux-thunk

redux-thunk也是我接触的第一个中间件,相对上手比较快,容易理解,它的代码也很短,最主要的核心函数如下:

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;

redux-thunk中间件通过判断action首先是否为一个函数。如果是,redux-thunk通过调用你的action函数,action函数就可以进行内部处理,在函数内部可以进行异步流处理,然后可以继续通过dispatch(action)进行同步数据的处理;如果不是,则通过next(action)调用下一个中间件或者是进入reducer。

其主要思想就是扩展action,使得action从一个对象变成一个函数,具体的例子代码可以参见我之前的项目的实践基于React的atm。在那个项目中是用了fetch返回promise进行处理。如果想要代码更加同步思维,可以搭配async/await方法进行回调处理。

redux-thunk处理异步方便,配合asyvc/await更可以使得action同步化。不过redux-thunk中action会因此变的复杂,后期可维护性下降;同时若多人协作,当自己的action调用了别人的action,别人action发生改动,则需要自己主动修改。

redux-saga

redux-saga相比其他的异步流中间件,将异步处理单独放在一处(可以将某个组件的异步处理放在同一个文件),不需要修改action,action还是同步。同时redux-saga的异步控制流程也很强大,比如对于竞态的处理就通过takeLatest()来处理。除此以外,redux-saga具体很好的可测试性。

我选择它主要是因为redux-saga可以让action保持纯对象,同时考虑到它的异步控制流程,以及可以通过封装方便我们的异步操作。

redux-saga的概览

redux-saga本质是把副作用 (Side effect,异步行为就是典型的副作用)看成一个”线程”,可以通过纯对象的action去触发它,当异步完成时可以触发action作为输出。

redux-saga基本的api包括Effect creators,Saga Helpers等。Effect creators包括fork,call,take,put,cancel等。我们关注的异步处理是通过call来完成,例如const response=yield call(fetch,url)就可以发起一次fetch请求,由于redux-saga进行了封装,因此response会收到fetch得到的promise的resolve(data)中的data对象,实现了同步的方式也来处理异步流。fork和call的不同之处在于,fork的调用是非阻塞的,我们通过可以用yield [fork(saga1),fork(saga2)]将不同的子saga挂载。take用来监听aciton,put相当于dispatch一个action,cancel用来取消fork。其他redux-saga还有很多的Effect creators可以参见官网。

项目中怎么用saga

在本项目中,使用redux-saga的时候,首先将不同组件的异步处理分布在各个子saga,然后通过一个合并saga的文件将各个saga进行fork。同时,在各个子saga中对异步处理进行抽象封装:

简化的发起文件:

1
2
3
4
5
6
7
let request = fetchData(url, successAction, failAction);
export function* watcher() {
while (true) {
const action = yield take(takeAction);
yield call(request, data);
}

简化的fetchData文件:

1
2
3
4
5
6
7
8
9
10
export function fetchData(url, successAction, failAction) {
return function*(data) {
try {
const response = yield call((arguments.length === 1) ? fetchPost : fetchGet, url, data);
//处理正常情况下,在此可以put(action);
} catch (e) {
//处理异常
}
}
}

通过我们的封装,我们可以看到异步执行过程都在fetchData()内部去执行,而fetchData()对于大部分组件是公用的,因此我们在子saga文件只需要配置具体的url,回调的action。可以看到,通过上述操作,我们将异步操作简化到配置参数级别。只要处理好fetchData()的兼容性,我们可以通过redux-saga很方便地进行异步编码,大大地提高开发效率。