为什么选择 redux-saga 中间件
Opened this issue · 1 comments
对于这个问题,其实免不了比较一下redux-thunk和redux-saga。
不管是thunk还是saga,都是redux的一个中间件。至于为什么要使用中间件,在redux中,reducer都是纯函数,由于是纯函数,所以操作的返回结果都是依赖于它的参数,执行过程不会产生副作用(即传什么,返回什么)。但是在实际的开发过程中,我们需要处理一些异步操作,Redux的作者将这些副作用的操作以中间件的形式提供给开发者,开发者可以自行选择或自己实现。
Redux-thunk
下面是thunk的实现:
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仅仅只是对传入的函数进行了执行操作,并不关心函数的主体内容。比如我们可以传入一个如下的异步操作来触发action:
store.dispatch(dispatch => {
// 开始请求
dispatch({ type: 'START' })
// 模拟请求,request返回一个promise
request(url)
.then(res => {
// 请求正常后进行验证操作
if (res.code === 'success') {
dispatch({ type: 'SUCCESS', payload: res.data })
}
})
.catch(error => {
// 请求错误后进行的操作
})
})
由于函数的实现比较复杂,同时函数的实现方法不固定,如果异步请求每次都需要定义一个action显然开发成本是很高的。所以thunk的缺点总结一下就是:
- 异步操作实现方法比较复杂
- 异步操作没有统一管理,太过分散
- 需要对数据进行复杂的模拟判断,不利于测试
Redux-saga
redux-saga是通过ES6中的generator实现的(babel的基础版本不包含generator语法,因此需要在使用saga的地方import ‘babel-polyfill’)。redux-saga本质是一个可以自执行的generator。
先来看一下saga官方提供的例子shoppingCat中saga/index.js文件。
import { take, put, call, fork, select, takeEvery, all } from 'redux-saga/effects'
import * as actions from '../actions'
import { getCart } from '../reducers'
import { api } from '../services'
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))
}
}
export function* watchGetProducts() {
/*
takeEvery will fork a new `getAllProducts` task on each GET_ALL_PRODUCTS actions
i.e. concurrent GET_ALL_PRODUCTS actions are allowed
*/
yield takeEvery(actions.GET_ALL_PRODUCTS, getAllProducts)
}
export function* watchCheckout() {
while (true) {
yield take(actions.CHECKOUT_REQUEST)
/*
***THIS IS A BLOCKING CALL***
It means that watchCheckout will ignore any CHECKOUT_REQUEST event until
the current checkout completes, either by success or by Error.
i.e. concurrent CHECKOUT_REQUEST are not allowed
TODO: This needs to be enforced by the UI (disable checkout button)
*/
yield call(checkout)
}
}
export default function* root() {
yield all([fork(getAllProducts), fork(watchGetProducts), fork(watchCheckout)])
}
着重看一下getAllProducts()这个异步操作方法,找到getAllProducts()对应的actions代码如下:
export const GET_ALL_PRODUCTS = 'GET_ALL_PRODUCTS'
export function getAllProducts() {
return {
type: GET_ALL_PRODUCTS,
}
}
上面的代码action与我们redux中同步action的key是统一的。
- redux-saga effect
redux-saga中的effect本质是一个特定的函数,返回纯文本对象。即通过effect返回的一个字符串,saga-middleware根据这个字符串来执行真正的异步操作,具体表现成如下形式(来自异步方案选型redux-saga 和 redux-thunk(async/await)):
异步操作-->Effect函数-->纯文本对象-->saga-middleware-->执行异步操作
- effects 的具体函数(参考redux-saga API)
总结一下saga的优点:
- 集中管理了异步操作方法,方便维护
- action是普通对象,与redux同步的action保持一致
- 可以在Effect中测试异步接口
- worker和watcher可以实现非阻塞异步调用,同时可以实现非阻塞调用下的事件监听
- 异步操作流程可控,可以取消相应的操作
总结
对比之下,redux-saga的优点还是很多的,如果排除学习成本的因素,更推荐使用redux-saga作为中间件进行状态管理。
以上都是个人通过各大博客的学习总结,仅用于个人学习,肯定有很多不对或理解不好的地方,希望能够帮我指正,感谢。。。