redux異步action解決方案

redux異步action解決方案

若是沒有中間件,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

1. redux-thunk


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;

2. redux-promise

使用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);
  };
}

3. Redux-saga

redux-saga 是一個用於管理應用程序 Side Effect(反作用,例如異步獲取數據,訪問瀏覽器緩存等)的 library,它的目標是讓反作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易緩存

3.1 基本使用

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)

3.2 聲明式effects,便於測試

爲了測試方便,在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']  
  }
}

3.3 構建複雜的控制流

saga能夠經過使用effect建立器、effect組合器、saga輔助函數來構建複雜的控制流。異步

effect建立器:async

  • take:阻塞性effect,等待store中匹配的action或channel中的特定消息。
  • put:非阻塞性effect,用來命令 middleware 向 Store 發起一個 action。
  • call:阻塞性effect,用來命令 middleware 以參數 args 調用函數 fn
  • fork:非阻塞性effect,用來命令 middleware 以 非阻塞調用 的形式執行 fn
  • select:非阻塞性effect,用來命令 middleware 在當前 Store 的 state 上調用指定的選擇器

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)  
        } 
       })
相關文章
相關標籤/搜索