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/redux-nowadays-from-actions-creators-to-sagas/

相關文章
相關標籤/搜索