前面咱們講解過redux框架和dva框架的基本使用,由於dva框架中effects模塊設計到了redux-saga中的知識點,可能有的同窗們會用dva框架,可是對redux-saga又不是很熟悉,今天咱們就來簡單的講解下saga框架的主要API和如何配合redux框架使用javascript
http://leonshi.com/redux-saga-in-chinese/index.htmlhtml
redux-saga-Demo做者仍是按照之前的風格,提供了兩個不一樣的版本,簡單的 CounterApp, 稍複雜的 TodoListjava
https://github.com/guangqiang-liu/redux-saga-counterAppreact
https://github.com/guangqiang-liu/redux-saga-todoListDemogit
redux-saga 是一個用於管理 Redux 應用異步操做的中間件(又稱異步 action)。 redux-saga 經過建立 Sagas 將全部的異步操做邏輯收集在一個地方集中處理,能夠用來代替 redux-thunk 中間件。github
這意味着應用的邏輯會存在兩個地方:redux
Reducers 負責處理 action 的 state 更新api
Sagas 負責協調那些複雜或異步的操做promise
Sagas是經過Generator函數來建立的,若是有不熟悉 Generator函數使用的,請查看阮老師對Generator的介紹服務器
Sagas 不一樣於thunks,thunks 是在action被建立時調用,而 Sagas只會在應用啓動時調用(但初始啓動的 Sagas 可能會動態調用其餘 Sagas),Sagas 能夠被看做是在後臺運行的進程,Sagas 監聽發起的action,而後決定基於這個 action來作什麼:是發起一個異步調用(好比一個 fetch 請求),仍是發起其餘的action到Store,甚至是調用其餘的 Sagas
在 redux-saga 的世界裏,全部的任務都通用 yield Effects 來完成(Effect 能夠看做是 redux-saga 的任務單元)。Effects 都是簡單的 Javascript 對象,包含了要被 Saga middleware 執行的信息(打個比方,你能夠看到 Redux action實際上是一個個包含執行信息的對象), redux-saga 爲各項任務提供了各類Effect建立器,好比調用一個異步函數,發起一個action到Store,啓動一個後臺任務或者等待一個知足某些條件的將來的 action
1、Saga 輔助函數
redux-saga提供了一些輔助函數,用來在一些特定的action 被髮起到Store時派生任務,下面我先來說解兩個輔助函數:takeEvery
和 takeLatest
例如:每次點擊 Fetch 按鈕時,咱們發起一個 FETCH_REQUESTED 的 action。 咱們想經過啓動一個任務從服務器獲取一些數據,來處理這個action
首先咱們建立一個將執行異步 action 的任務:
import { call, put } from 'redux-saga/effects' export function* fetchData(action) { try { const data = yield call(Api.fetchUser, action.payload.url); yield put({type: "FETCH_SUCCEEDED", data}); } catch (error) { yield put({type: "FETCH_FAILED", error}); } }
而後在每次 FETCH_REQUESTED action 被髮起時啓動上面的任務
import { takeEvery } from 'redux-saga' function* watchFetchData() { yield* takeEvery("FETCH_REQUESTED", fetchData) }
注意:上面的 takeEvery 函數可使用下面的寫法替換
function* watchFetchData() { while(true){ yield take('FETCH_REQUESTED'); yield fork(fetchData); } }
在上面的例子中,takeEvery 容許多個 fetchData 實例同時啓動,在某個特定時刻,咱們能夠啓動一個新的 fetchData 任務, 儘管以前還有一個或多個 fetchData 還沒有結束
若是咱們只想獲得最新那個請求的響應(例如,始終顯示最新版本的數據),咱們可使用 takeLatest 輔助函數
import { takeLatest } from 'redux-saga' function* watchFetchData() { yield* takeLatest('FETCH_REQUESTED', fetchData) }
和takeEvery不一樣,在任什麼時候刻 takeLatest 只容許執行一個 fetchData 任務,而且這個任務是最後被啓動的那個,若是以前已經有一個任務在執行,那以前的這個任務會自動被取消
2、Effect Creators
redux-saga框架提供了不少建立effect的函數,下面咱們就來簡單的介紹下開發中最經常使用的幾種
take(pattern)
take函數能夠理解爲監聽將來的action,它建立了一個命令對象,告訴middleware等待一個特定的action, Generator會暫停,直到一個與pattern匹配的action被髮起,纔會繼續執行下面的語句,也就是說,take是一個阻塞的 effect
用法:
function* watchFetchData() { while(true) { // 監聽一個type爲 'FETCH_REQUESTED' 的action的執行,直到等到這個Action被觸發,纔會接着執行下面的 yield fork(fetchData) 語句 yield take('FETCH_REQUESTED'); yield fork(fetchData); } }
put(action)
put函數是用來發送action的 effect,你能夠簡單的把它理解成爲redux框架中的dispatch函數,當put一個action後,reducer中就會計算新的state並返回,注意: put 也是阻塞 effect
用法:
export function* toggleItemFlow() { let list = [] // 發送一個type爲 'UPDATE_DATA' 的Action,用來更新數據,參數爲 `data:list` yield put({ type: actionTypes.UPDATE_DATA, data: list }) }
call(fn, ...args)
call函數你能夠把它簡單的理解爲就是能夠調用其餘函數的函數,它命令 middleware 來調用fn 函數, args爲函數的參數,注意: fn 函數能夠是一個 Generator 函數,也能夠是一個返回 Promise 的普通函數,call 函數也是阻塞 effect
用法:
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) export function* removeItem() { try { // 這裏call 函數就調用了 delay 函數,delay 函數爲一個返回promise 的函數 return yield call(delay, 500) } catch (err) { yield put({type: actionTypes.ERROR}) } }
fork(fn, ...args)
fork 函數和 call 函數很像,都是用來調用其餘函數的,可是fork函數是非阻塞函數,也就是說,程序執行完 yield fork(fn, args)
這一行代碼後,會當即接着執行下一行代碼語句,而不會等待fn函數返回結果後,在執行下面的語句
用法:
import { fork } from 'redux-saga/effects' export default function* rootSaga() { // 下面的四個 Generator 函數會一次執行,不會阻塞執行 yield fork(addItemFlow) yield fork(removeItemFlow) yield fork(toggleItemFlow) yield fork(modifyItem) }
select(selector, ...args)
select 函數是用來指示 middleware調用提供的選擇器獲取Store上的state數據,你也能夠簡單的把它理解爲redux框架中獲取store上的 state數據同樣的功能 :store.getState()
用法:
export function* toggleItemFlow() { // 經過 select effect 來獲取 全局 state上的 `getTodoList` 中的 list let tempList = yield select(state => state.getTodoList.list) }
3、createSagaMiddleware()
createSagaMiddleware 函數是用來建立一個 Redux 中間件,將 Sagas 與 Redux Store 連接起來
sagas 中的每一個函數都必須返回一個 Generator 對象,middleware 會迭代這個 Generator 並執行全部 yield 後的 Effect(Effect 能夠看做是 redux-saga 的任務單元)
用法:
import {createStore, applyMiddleware} from 'redux' import createSagaMiddleware from 'redux-saga' import reducers from './reducers' import rootSaga from './rootSaga' // 建立一個saga中間件 const sagaMiddleware = createSagaMiddleware() // 建立store const store = createStore( reducers, 將sagaMiddleware 中間件傳入到 applyMiddleware 函數中 applyMiddleware(sagaMiddleware) ) // 動態執行saga,注意:run函數只能在store建立好以後調用 sagaMiddleware.run(rootSaga) export default store
4、middleware.run(sagas, ...args)
動態執行sagas,用於applyMiddleware階段以後執行sagas
注意:動態執行saga語句 middleware.run(sagas)
必需要在store建立好以後才能執行,在 store 以前執行,程序會報錯
**index.js **
import React from 'react'; import ReactDOM from 'react-dom'; import {createStore, applyMiddleware} from 'redux' import createSagaMiddleware from 'redux-saga' import rootSaga from './sagas' import Counter from './Counter' import rootReducer from './reducers' const sagaMiddleware = createSagaMiddleware() let middlewares = [] middlewares.push(sagaMiddleware) const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore) const store = createStoreWithMiddleware(rootReducer) sagaMiddleware.run(rootSaga) const action = type => store.dispatch({ type }) function render() { ReactDOM.render( <Counter value={store.getState()} onIncrement={() => action('INCREMENT')} onDecrement={() => action('DECREMENT')} onIncrementAsync={() => action('INCREMENT_ASYNC')} />, document.getElementById('root') ) } render() store.subscribe(render)
sagas.js
import { put, call, take,fork } from 'redux-saga/effects'; import { takeEvery, takeLatest } from 'redux-saga' export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); function* incrementAsync() { // 延遲 1s 在執行 + 1操做 yield call(delay, 1000); yield put({ type: 'INCREMENT' }); } export default function* rootSaga() { // while(true){ // yield take('INCREMENT_ASYNC'); // yield fork(incrementAsync); // } // 下面的寫法與上面的寫法上等效 yield* takeEvery("INCREMENT_ASYNC", incrementAsync) }
reducer.js
export default function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 case 'INCREMENT_ASYNC': return state default: return state } }
從上面的代碼結構能夠看出,redux-saga的使用方式仍是比較簡單的,相比較以前的redux框架的CounterApp,多了一個sagas的文件,reducers文件仍是以前的使用方式
本文所講解的基本上是redux-saga框架在開發中最常使用到的經常使用API,還有不少不經常使用API,請參照redux-saga官方文檔:http://leonshi.com/redux-saga-in-chinese/docs/api/index.html
若是同窗們看到文章這裏,仍是對redux-saga框架的基本使用不熟悉,概念模糊,建議看看做者提供的Demo示例
做者建議:同窗們能夠將做者以前講解的redux框架和redux-saga框架對比來學習理解,這樣更清楚他們之間的區別和聯繫。