Redux:从action到saga

前端应用消失的部分

一个现代的、使用了redux的前端应用架构能够这样描述:html

  1. 一个存储了应用不可变状态(state)的store
  2. 状态(state)能够被绘制在组件里(html或者其余的东西)。这个绘制方法一般是简单并且可测试的(并不老是如此)纯方法。
const render = (state) => components
  1. 组件能够给store分发action
  2. 使用reducer这种纯方法来根据就的状态返回新的状态
const reducer = (oldState, action) => newState
  1. 从一再来一次

这看起来容易理解。可是当须要处理异步的action(在函数式编程里称为反作用)的时候事情就没有这么简单了。前端

为了解决这个问题,redux建议使用中间件(尤为是thunk)。基本上,若是你须要出发反作用(side effects),使用一种特定的action生成方法:一种返回一个方法的方法,能够实现任意的异步访问并分发任意你想要的action。编程

使用这个方式会很快致使action生成方法变得复杂并难以测试。这个时候就须要redux-saga了。在redux-saga里saga就是一个可声明的组织良好的反作用实现方式(超时,API调用等等。。)因此不用再用redux-thunk中间件来写,咱们用saga来发出action并yield反作用。redux

这样不复杂?action creator这样的写法不是更简单?

虽然看起来是这样的,可是NO!后端

咱们来看看如何写一个action creator来获取后端数据并分发到redux store。promise

function loadTodos() {
  return dispatch => {
    dispatch({ type: 'FETCH_TOTOS' });
    fetch('/todos').then(todos => {
      dispatch({ type: 'FETCH_TODOS', payload: todos });
    });
  }
}

这是最简单的thunk action creator了,而且如你所见,惟一测试这个代码的方法是模拟获取数据的方法。架构

咱们来看看用saga代替action creator获取todo数据的方法:app

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

function* loadTodos() {
  yield put({ type: 'FETCH_TODOS' });
  const todos = yield call(fetch, '/todos');
  yield put({ type: 'FETCH_TODOS', payload: todos });
}

正如你所见一个saga就是一个生成反作用(side effects)的generator。我(做者)更加倾向于把整个generator叫作纯generator,由于它不会实际执行反作用,只会生成一个要执行的反作用的描述。在上面的例子中我用了两种反作用:异步

  • 一个put反作用,它会给redux store分发一个action。
  • 一个call反作用,它会执行一个异步的方法(promise,cps后者其余的saga)。

如今,测试这个saga就很是的容易了:ide

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

const mySaga = loadTodos();
const myTodos = [{ message: 'text', done: false }];
mySaga.next();
expect(mySaga.next().value).toEqual(put({ type: 'FETCH_TOTOS' }));
expect(mySaga.next().value).toEqual(call(fetch, '/todos'));
expect(mySaga.next().value).toEqual(put({ type: 'FETCH_TODOS', payload: myTodos }));

触发一个saga

thunk的action creator在分发它返回的方法的时候就会触发。saga不一样,它们就像是运行在后台的守护任务(daemon task)同样有本身的运行逻辑(by Yasine Elouafi redux-saga的做者)。

因此,咱们来看看如何在redux应用里添加saga。

import { createStore, applyMiddleware } from 'redux';
import sagaMiddleware from 'redux-saga';

const createStoreWithSaga = applyMiddleware(
  sagaMiddleware([loadTodos])
)(createStore);

let store = createStoreWithSaga(reducer, initialState);

绑定saga

一个saga自己就是一个反作用,就如同redux的reducer同样,绑定saga很是简单(可是很好的理解ES6的generator是很是有必要的)。

在以前的例子里,loadTodos saga在一开始就会触发。可是,若是咱们想要每次一个action分发到store的时候触发saga要怎么作呢?看代码:

import { fork, take } from 'redux-saga';

function* loadTodos() {
  yield put({ type: 'FETCHING_TODOS' });
  const todos = yield call(fetch, '/todos');
  yield put({ type: 'FETCHED_TODOS', payload: todos });
}

function* watchTodos() {
  while(yield take('FETCH_TODOS')) {
    yield fork(loadTodos);
  }
}

// 咱们须要更新saga常量 createStoreWithSaga = applyMiddleware(
  sagaMiddleware([watchTodos])
)(createStore);

上例用到了两个特殊的effect:

  • take effect,它会等待分发redux action的时候执行
  • fork effect, 它会触发另一个effect,在子effect开始以前就会执行

结语

给前端应用添加redux和redux-saga的流程是这样的:

  1. store持有一个应用的state。
  2. state会被绘制到组件上(html或者其余的什么)。它是一个简单可测试的方法:
const render = (state) => components
  1. 组件会触发修改store的action。
  2. state使用reducer这样的纯方法来修改的。
const reducer = (oldState, action) => newState
  1. 也许某些effect会被一个action或者其余的effect触发。
function* saga() { yield effect; }
  1. 从1开始。

原文连接:https://riad.blog/2015/12/28/...

相关文章
相关标签/搜索