KayneWang/blog

为什么选择 redux-saga 中间件

Opened this issue · 1 comments

为什么选择 redux-saga 中间件

对于这个问题,其实免不了比较一下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-->执行异步操作

总结一下saga的优点:

  • 集中管理了异步操作方法,方便维护
  • action是普通对象,与redux同步的action保持一致
  • 可以在Effect中测试异步接口
  • worker和watcher可以实现非阻塞异步调用,同时可以实现非阻塞调用下的事件监听
  • 异步操作流程可控,可以取消相应的操作

总结

对比之下,redux-saga的优点还是很多的,如果排除学习成本的因素,更推荐使用redux-saga作为中间件进行状态管理。

以上都是个人通过各大博客的学习总结,仅用于个人学习,肯定有很多不对或理解不好的地方,希望能够帮我指正,感谢。。。