Redus-saga是一個redux的中間件,主要用來簡便而優雅的處理redux應用裏的反作用(side effect相對於pure function這類概念而言的)。它之因此能夠作到這一點主要是使用了ES6裏的一個語法:Generator。使用Generator能夠像寫同步的代碼同樣編寫異步代碼,這樣更加容易測試。javascript
在咱們更深刻以前,「saga」這個名字在計算機科學的歷史上早已存在,並非只用於javascript裏的。Saga能夠簡要的歸納爲一種處理長時間運行的事務,而且這種事務會有反作用或者失敗的可能。每個咱們但願完成的事務,都須要一個反事務在出錯的時候把事務恢復到當前事務發生以前的樣子。若是有興趣瞭解更多Sage,我(做者)推薦你看看Caitie McCaffrey的這段演講,演講的題目是《實踐Saga模式》。另外能夠參閱Roman Liutikov的博客,叫作《Saga模式的迷惑之處》。java
如今咱們能夠建立一個新的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是純的,並且咱們的異步流程很容易測試。若是你要學到更多,下面的資源會有幫助: