一個現代的、使用了redux的前端應用架構能夠這樣描述:html
const render = (state) => components
const reducer = (oldState, action) => newState
這看起來容易理解。可是當須要處理異步的action(在函數式編程裏稱爲反作用)的時候事情就沒有這麼簡單了。前端
爲了解決這個問題,redux建議使用中間件(尤爲是thunk)。基本上,若是你須要出發反作用(side effects),使用一種特定的action生成方法:一種返回一個方法的方法,能夠實現任意的異步訪問並分發任意你想要的action。編程
使用這個方式會很快致使action生成方法變得複雜並難以測試。這個時候就須要redux-saga了。在redux-saga裏saga就是一個可聲明的組織良好的反作用實現方式(超時,API調用等等。。)因此不用再用redux-thunk中間件來寫,咱們用saga來發出action並yield反作用。redux
雖然看起來是這樣的,可是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,由於它不會實際執行反作用,只會生成一個要執行的反作用的描述。在上面的例子中我用了兩種反作用:異步
如今,測試這個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 }));
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自己就是一個反作用,就如同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的流程是這樣的:
const render = (state) => components
const reducer = (oldState, action) => newState
function* saga() { yield effect; }
原文連接:https://riad.blog/2015/12/28/redux-nowadays-from-actions-creators-to-sagas/