简要介绍:
redux中的action仅支持原始对象(plain object),处理有副作用的action,需要使用中间件。中间件可以在发出action,到reducer函数接受action之间,执行具有副作用的操作。
之前一直使用redux-thunk处理异步等副作用操作,在action中处理异步等副作用操作,此时的action是一个函数,以dispatch,getState作为形参,函数体内的部分可以执行异步。通过redux-thunk来处理异步,action可谓是多种多样,不利于维护。
本文主要介绍一下redux-saga ,并在此基础上比较redux-thunk(async/await)
1 . redux-thunk 的使用与缺点
(1)redux-thunk的使用
thunk是redux作者给出的中间件,实现极为简单,10多行代码:
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;
这几行代码做的事情也很简单,判别action的类型,如果action是函数,就调用这个函数,调用的步骤为:
action(dispatch, getState, extraArgument);
发现实参为dispatch和getState,因此我们在定义action为thunk函数是,一般形参为dispatch和getState。
(2)redux-thunk的缺点
thunk的缺点也是很明显的,thunk仅仅做了执行这个函数,并不在乎函数主体内是什么,也就是说thunk使得redux可以接受函数作为action,但是函数的内部可以多种多样。比如下面是一个获取商品列表的异步操作所对应的action
export default ()=>(dispatch)=>{
fetch('/api/goodList',{ //fecth返回的是一个promise
method: 'get',
dataType: 'json',
}).then(function(json){
var json=JSON.parse(json);
if(json.msg==200){
dispatch({type:'init',data:json.data});
}
},function(error){
console.log(error);
});
};
从这个具有副作用的action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护。
action不易维护的原因:
I)action的形式不统一
II)就是异步操作太为分散,分散在了各个action中
2 .redux-saga的使用
在redux-saga中,action是plain object(原始对象),并且集中处理了所有的异步操作,下面我们以redux-saga的官方例子shopping-cart为例,来说说redux-saga的使用。
shopping-cart例子很简单,展示的是如下过程:
商品列表——>添加商品——>购物车——>付款
显然,这里有两个明显的异步操作需要执行:
获取商品列表和付款
用getAllProducts()和checkout()来表示,如果用thunk,那么这两个异步的操作分属于两个不同的action中,但是在saga中,它们是集中处理的。
使用saga,我们先生成一个集中处理异步的saga.js文件:
...
...
export function* getAllProducts() {
const products = yield call(api.getProducts)
yield put(actions.receiveProducts(products))
}
export function* checkout() {
try {
const cart = yield select(getCart)
yield call(api.buyProducts, cart)
yield put(actions.checkoutSuccess(cart))
} catch (error) {
yield put(actions.checkoutFailure(error))
}
}
...
...
抛去其他部分(具体用法我们待会解释),我们看到在saga.js中集中了这两个异步操作getAllProducts()和checkout()
此外,在saga中的action跟原始对象是完全相同的,我们来看saga中的action creator :
export const GET_ALL_PRODUCTS = 'GET_ALL_PRODUCTS'
export function getAllProducts() {
return {
type: GET_ALL_PRODUCTS,
}
}
上述的action creator中,创建的action是一个plain object,跟我们在redux中同步action的样式是统一的。
3 . redux-saga的API
redux-saga是通过ES6中的generator实现的(babel的基础版本不包含generator语法,因此需要在使用saga的地方import ‘babel-polyfill’)。redux-saga本质是一个可以自执行的generator。
(1)redux-saga中的Effect
redux-saga中定义了Effect,Effect是什么呢,本质就是一个特定的函数,返回的是纯文本对象。简单理解,通过Effect函数,会返回一个字符串,saga-middleware根据这个字符串来执行真正的异步操作,可以具体表现成如下形式:
异步操作——>Effect函数——>纯文本对象——>saga-middleware——>执行异步操作
因为Effect的存在,方便saga测试异步操作。
(2)Effect具体函数
Effect函数有很多个,在redux-saga/effects提供,主要包括call,fork,put,take,select等,它们都与middleware中的操作一一对应。
I)call 和 fork
call和fork表示异步调用,其中call表示的是阻塞调用,fork表示的是非阻塞调用。
II)put和select
put对应的是middleware中的dispatch方法,参数是一个plain object,一般在异步调用返回结果后,接着执行put。select相当于getState,用于获取store中的相应部分的state。
III)take、takeEvery、takeLatest
redux-saga中如果在非阻塞调用下(fork),那么遵循的是worker/watcher模式,通过take可以监听某个action是否被发起,此外通过take结合fork,可以实现takeEvery和takeLatest的效果。
如果一个异步操作的action被发起多次,takeEvery会执行多次action,而takeLatest只会执行最近的一次。
4 . redux-saga的优缺点
优点:
(1)集中处理了所有的异步操作,异步接口部分一目了然
(2)action是普通对象,这跟redux同步的action一模一样
(3)通过Effect,方便异步接口的测试
(4)通过worker 和watcher可以实现非阻塞异步调用,并且同时可以实
现非阻塞调用下的事件监听
(5) 异步操作的流程是可以控制的,可以随时取消相应的异步操作。
缺点:太复杂,学习成本较高
5 .redux-thunk(async/await)
如果在redux-thunk中,action是一个async函数,通过约定一些异步操作时所遵循的规则,即使不能集中展示所有的异步操作,但是通过约定我们可以减少函数action的复杂度,我们最初获取商品列表的那个action,可以改写成:
export default function(){
return function(dispatch){
await result=fetch(...)
result.then(...)
}
};
总之,如果不涉及学习成本等,react-saga还是比较推荐使用的。