初识Redux-Saga

Redus-saga是一个redux的中间件,主要用来简便而优雅的处理redux应用里的反作用(side effect相对于pure function这类概念而言的)。它之因此能够作到这一点主要是使用了ES6里的一个语法:Generator。使用Generator能够像写同步的代码同样编写异步代码,这样更加容易测试。javascript

在咱们更深刻以前,“saga”这个名字在计算机科学的历史上早已存在,并非只用于javascript里的。Saga能够简要的归纳为一种处理长时间运行的事务,而且这种事务会有反作用或者失败的可能。每个咱们但愿完成的事务,都须要一个反事务在出错的时候把事务恢复到当前事务发生以前的样子。若是有兴趣了解更多Sage,我(做者)推荐你看看Caitie McCaffrey的这段演讲,演讲的题目是《实践Saga模式》。另外能够参阅Roman Liutikov的博客,叫作《Saga模式的迷惑之处》java

咱们为何要用Redux-saga

如今咱们能够建立一个新的react-redux应用,咱们会使用redux-thunk和redux-saga处理异步的action。因此咱们为何要用redux-saga呢?react

正如文档里所说:git

与redux-thunk相反,你不会跌入回调地狱,你能够很容易的测试你的异步流程并且action一直保持pure(纯的状态)。

咱们来对比一下saga和thunk的用法来更深刻的理解上面那句话的内容。假设场景为:用户点击按钮,发出一个http请求来获取数据。
Redux thunk的写法:github

import {
  API_BUTTON_CLICK,
  API_BUTTON_CLICK_SUCCESS,
  API_BUTTON_CLICK_ERROR,
} from './actions/consts';
import { getDataFromAPI } from './api';

const getDataStarted = () => ({type: API_BUTTON_CLICK});
const getDataSuccess = data => ({type: API_BUTTON_CLICK_SUCESS, payload: data});
const getDataError = message => ({type: API_BUTTON_CLICK_ERROR, payload: message});

const getDataFromAPI = () => {
  return dispatch => {
    dispatch(getDataStarted());

    getDataFromAPI()
      .then(data => {
        dispatch(getUserSucess(data));
      }).fail(err => {
        dispatch(getDataError(err.message));
      })
  }
}

这里咱们有一个叫作getDataFromAPI()的方法来建立action。当用户点击按钮开始了异步流程的时候“redux

  • 首先触发一个action让咱们的store知道咱们要发起一个异步请求(dispatch(getDataStarted())了。segmentfault

  • 接着咱们实际发出了API请求,这个请求会返回一个promise。api

  • 而后咱们会在成功接受到请求数据的时候触发成功的action,有错误的时候触发错误的action。promise

Redux saga的写法:异步

import { call, put, takeEvery } from 'redux-saga/effects';
import {
    API_BUTTON_CLICK,
    API_BUTTON_CLICK_SUCCESS,
    API_BUTTON_CLICK_ERROR,
} from './actoins/consts';
import { getDataFromAPI } from './api';

export function* apiSideEffect(action) {
    try{
        const data = yield call(getDataFromaAPI);
        yield put({ type: API_BUTTON_CLICK_SUCESS, payload: data});
    } catch(e) {
        yield put({type: API_BUTTON_CLICK_ERROR, payload: e.message});
    }
}

// the 'watcher' -on every `API_BUTTON_CLICK` action, run our side effect 
export function* apiSaga() {
    yield takeEvery(API_BUTTON_CLICK, apiSideEffect);
}

流程基本上都是同样的,只是代码看起来略有不一样:

  • 与thunk例子不一样的是,咱们没有使用dispatch而是用了put(咱们能够认为二者是等价的)。

  • 咱们有一个监听方法一直监听着“start”方法。它只会在按钮点击以后触发一个类型为API_BUTTON_CLICK的redux action。

  • 咱们用redux-saga的call effect(专有名词,效果的意思。与side effect的effect赞成)来从异步的方法(promise,不一样的saga等)里面获取数据。

很是简单,对吧。在某些按钮的点击事件里,咱们会请求某些终端来获取数据。若是成功了,就发起一个新的,payload就是数据的action。不然就发出一个带有error消息的action。

同时,须要注意认真的理解上面的例子很是重要。不是说thunk的例子难以理解,可是咱们却摆脱了返回方法或者promise链的麻烦。咱们还能够只简单用一个try-catch来处理任何的异步错误。而后put(或者dispatch)一个action来通知reducer。

其次,更重要的是,咱们的saga side effect(saga反作用)是纯的(pure)。这是由于call(getDataFromAPI)并不实际执行API请求,它只是返回一个纯对象:{type: 'CALL', func, args}。实际的请求已经由redux-saga中间件执行,而且会把返回值带到generator里(因此须要用yeild关键字)或者抛出一个异常,若是有的话。

掌握了以上概念之后,你就会明白下面的测试为何这么简单:

import { call, put } from 'redux-saga/effects';

import { API_BUTTON_CLICK_SUCCESS, } from './actions/consts';
import { getDataFromAPI } from './api';

it('apiSideEffect - fetches data from API and dispatches a success action', () => {
    const generator = apiSideEffect();

    expect(generator.next().value).toEqual(call(getDataFromAPI));

    expect(generator.next().value).toEqual(put({type: API_BUTTON_CLICK_SUCCESS }));

    expect(generator.next()).toEqual({done: true, value: undefined});
});

然而上面的测试代码有一点点小问题。咱们须要模拟getDataFromAPI方法的调用,其余在上面语句块的方法也须要模拟出来。这也许不是什么大的工做量,可是随着咱们的action数量的增加,复杂度也会增长,相似上面的测试最好避免。

本文但愿讲清楚redux-saga三个要点。咱们不会再遇到回调地狱,咱们的action是纯的,并且咱们的异步流程很容易测试。若是你要学到更多,下面的资源会有帮助:

原文地址:https://engineering.universe....

相关文章
相关标签/搜索