若是沒有中間件,store.dispatch只能接收一個普通對象做爲action。在處理異步action時,咱們須要在異步回調或者promise函數then內,async函數await以後dispatch。redux
dispatch({ type:'before-load' }) fetch('http://myapi.com/${userId}').then({ response =>dispatch({ type:'load', payload:response }) })
這樣作確實能夠解決問題,特別是在小型項目中,高效,可讀性強。 但缺點是須要在組件中寫大量的異步邏輯代碼,不能將異步過程(例如異步獲取數據)與dispatch抽象出來進行復用。而採用相似redux-thunk之類的中間件可使得dispatch可以接收不單單是普通對象做爲action。例如:api
function load(userId){ return function(dispatch,getState){ dispatch({ type:'before-load' }) fetch('http://myapi.com/${userId}').then({ response =>dispatch({ type:'load', payload:response }) }) } } //使用方式 dispatch(load(userId))
使用中間件可讓你採用本身方便的方式dispatch異步action,下面介紹常見的三種。promise
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
使用redux-promise能夠將action或者action的payload寫成promise形式。 源碼:瀏覽器
export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload .then(result => dispatch({ ...action, payload: result })) .catch(error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); }) : next(action); }; }
redux-saga
是一個用於管理應用程序 Side Effect(反作用,例如異步獲取數據,訪問瀏覽器緩存等)的 library,它的目標是讓反作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易緩存
import { call, put, takeEvery, takeLatest} from 'redux-saga/effects'; //複雜的異步流程操做 function* fetchUser(action){ try{ const user = yield call(API.fetchUser, action.payload); yield put({type:"USER_FETCH_SUCCEEDED",user:user}) }catch(e){ yield put({type:"USER_FETCH_FAILED",message:e.message}) } } //監聽dispatch,調用相應函數進行處理 function* mainSaga(){ yield takeEvery("USER_FETCH_REQUESTED",fetchUser); } //在store中注入saga中間件 import {createStore,applyMiddleware} from 'redux'; import createSagaMiddleware from 'redux-saga'; import reducer from './reducers'; import mainSaga from './mainSaga'; const sagaMiddleware = createSagaMiddleware(); const store = createStore(reducer,initalState,applyMiddleware(sagaMiddleware)); sagaMiddleware.run(mainSaga)
爲了測試方便,在generator中不當即執行異步調用,而是使用call、apply等effects建立一條描述函數調用的對象,saga中間件確保執行函數調用並在響應被resolve時恢復generator。app
function* fetchProducts() { const products = yield Api.fetch('/products') dispatch({ type: 'PRODUCTS_RECEIVED', products }) } //便於測試 function* fetchProducts() { const products = yield call(Api.fetch, '/products') //便於測試dispatch yield put({ type: 'PRODUCTS_RECEIVED', products }) // ... } // Effect -> 調用 Api.fetch 函數並傳遞 `./products` 做爲參數 { CALL: { fn: Api.fetch, args: ['./products'] } }
saga能夠經過使用effect建立器、effect組合器、saga輔助函數來構建複雜的控制流。異步
effect建立器:async
args
調用函數 fn
。fn
effect組合器:ide
race:阻塞性effect:用來命令 middleware 在多個 Effect 間運行 競賽(Race)函數
function fetchUsersSaga { const { response, cancel } = yield race({ response: call(fetchUsers), cancel: take(CANCEL_FETCH) }) }
all: 當 array 或 object 中有阻塞型 effect 的時候阻塞,用來命令 middleware 並行地運行多個 Effect,並等待它們所有完成
function* mySaga() { const [customers, products] = yield all([ call(fetchCustomers), call(fetchProducts) ]) }
effect輔助函數:
takeEvery:非阻塞性effect,在發起(dispatch)到 Store 而且匹配 pattern
的每個 action 上派生一個 saga
const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() { while (true) { const action = yield take(patternOrChannel) yield fork(saga, ...args.concat(action)) } })
takeLatest:非阻塞性,在發起到 Store 而且匹配 pattern
的每個 action 上派生一個 saga
。並自動取消以前全部已經啓動但仍在執行中的 saga
任務。
const takeLatest = (patternOrChannel, saga, ...args) => fork(function*() { let lastTask while (true) { const action = yield take(patternOrChannel) if (lastTask) { yield cancel(lastTask) // 若是任務已經結束,cancel 則是空操做 } lastTask = yield fork(saga, ...args.concat(action)) } })
throttle:非阻塞性,在 ms
毫秒內將暫停派生新的任務
const throttle = (ms, pattern, task, ...args) => fork(function*() { const throttleChannel = yield actionChannel(pattern) while (true) { const action = yield take(throttleChannel) yield fork(task, ...args, action) yield delay(ms) } })