日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

redux功能強大的Middleware中間件使用學習_React

作者:文奇 ? 更新時間: 2022-11-01 編程語言

引言

上一節我們學習了redux在實際項目的應用細節,這一節我們來學習redux中一個很重要的概念:中間件。我們會簡單實現一個記錄的中間件, 然后學習redux-saga這個異步請求中間件。

redux中的Middleware

redux中的中間件提供的是位于 action 被發起之后,到達 reducer 之前的擴展點。 你可以利用 Redux middleware 來進行日志記錄、創建崩潰報告、調用異步接口或者路由等等。

記錄日志

試想一下,如果我們的redux在每一次dispatch的時候都可以記錄下此次發生的action以及dispatch結束后的store。那么在我們的應用 出現問題的時候,我們就可以輕松的查閱日志找出是哪個action導致了state不正確。那么我們怎樣通過redux實現它呢?

手動記錄

最直接的解決方案就是在每次調用 store.dispatch(action) 前后手動記錄被發起的 action 和新的 state。假如你在創建一個action時這樣調用:

store.dispatch(addTodo('use Redux'))

為了記錄這個 action 以及產生的新的 state,你可以通過這種方式記錄日志:

let action = addTodo('Use Redux')
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

那么很自然的就能想到可以封裝為一個函數在各處調用:

function dispatchAndLog(store, action) {
  console.log('dispatching', action)
  store.dispatch(action)
  console.log('next state', store.getState())
} 
dispatchAndLog(store, addTodo('Use Redux'))

但是這樣我們還是需要每次導入一個外部方法,那么如果我們直接去替換store實例中的dispatch函數呢?

let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

其實到這里我們想要實現的功能已經完成了,但是距離Middleware實際使用的方法還是有不小的差距, 同時我們這里只能對dispatch的擴展時十分有限的,如果我想對其添加其他的功能,又該怎么實現呢? 首先可以確定的是我們需要將每一個功能分離開來,我們希望的時一個功能對應一個模塊,那么當我們想添加其他的模塊時,應該是這樣的:

function patchStoreToAddLogging(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}
// 崩潰報告模塊
function patchStoreToAddCrashReporting(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action)
    } catch (err) {
      console.error('捕獲一個異常!', err)
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState()
        }
      })
      throw err
    }
  }
}

然后我們可以在store中使用它們:

patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)

那么有沒有一種更好的代碼組織方式呢?此前,我們使用dispatchAndLog替換了dispatch, 如果我們不這樣做,而是在函數中返回新的dispatch呢?

function logger(store) {
  let next = store.dispatch
  // 我們之前的做法:
  // store.dispatch = function dispatchAndLog(action) {
  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

然后我們在外部提供方法將它替換到store.dispatch中。

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()
  // 在每一個 middleware 中變換 dispatch 方法。
  middlewares.forEach(middleware =>
    store.dispatch = middleware(store)
  )
}

其實到了這里我們的中間件功能已經大體實現,如果想后續繼續深入請參考redux官方文檔

redux-saga

接下來我們來看管理應用程序副作用的中間件reudx-saga。他在redux中有很多使用場景,但是我們使用最多的還是用它來進行網絡請求。

redux-saga使用了ES6的Generator功能,讓異步的流程更易于讀取,寫入和測試。因此我們首先了解一下generator函數是什么?

Generator函數

形式上,Generator函數是一個普通函數,但是有兩個特征。

一是,function關鍵字與函數名之間有一個星號;

二是,函數體內部使用yield表達式,定義不同的內部狀態.

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
const hw = helloWorldGenerator();

Generator 函數的調用方法與普通函數一樣,也是在函數名后面加上一對圓括號。不同的是,調用 Generator 函數后,該函數并不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象。

下一步,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)為止。

換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法可以恢復執行

hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

實際使用場景

現在來看一個項目中的實際使用

//查詢搜索列表
const requestLists = function*({ page, keyword, callback }) {
  try {
    appLoading(); // 展現加載框
    const body = {
      keyword: keyword,
      page: page,
      size: size,
    };
    const result = yield handleData.get(DataUrls.searchLists, body) // 發送網絡請求
    if (result && result.success) {
      yield put(Action.fetchSearchListDone(lists)); // 請求成功保存數據
      callback && callback();
    } else {
      showModal((result && result.message) || '系統繁忙,請稍后'); 
      yield put(Action.fetchSearchListFailure(result)); //請求失敗錯誤處理
    }
  } catch (err) {
    yield put(Action.fetchSearchListFailure(err)); //錯誤處理
    showModal('系統繁忙,請稍后');
  } finally {
    appFinish(); //關閉加載框
  }
};

上面是一個比較完整的請求處理過程,從發送請求到成功或失敗處理都有包含到。

對上面的代碼做一個解釋,在這個函數中我們首先使用yield發起一個異步請求,這時middleware 會暫停 Saga,直到請求完成。 一旦完成后,不管是成功或者失敗,middleware 會恢復 Saga 接著執行,直到遇到下一個 yield。當 try 報錯時, 會執行到catch去捕獲異常, 在這里遇到下一個yield,調用請求失敗的Action,傳入失敗原因。請求成功時遇到下一個 yield 對象,調用請求成功的Action,傳入結果。

put 就是我們稱作副作用的一個例子。副作用是一些簡單 Javascript 對象,包含了要被 middleware 執行的指令。 當 middleware 拿到一個被 Saga yield 的副作用,它會暫停 Saga,直到副作用執行完成,然后 Saga 會再次被恢復。

接下來我們需要去啟動這個saga,為了做到這一點,我們將添加一個 listSaga,負責啟動其他的 Sagas。在同一個文件中:

const listSagas = function* listSagas() {
  yield all([
    takeEvery('LIST_REQUESTLIST', requestLists),
  ]);
};
export default listSagas;

其中的輔助函數takeEvery用于監聽所有的LIST_REQUESTLISTaction,在action執行的時候去啟動相應的requestLists任務。 定義一個listSagas的原因就是我們這個文件中可能遠不至這一個副作用函數,當定義了多個的時候,我們可以在all中添加一個takeEvery, 這樣就會有兩個Generators同時啟動。在實際項目中因為項目所分的模塊可能會有很多,因此對每個模塊都定義一個sagas是很有必要的, 最終在sagas的最外層定義一個index.js文件用來將我們的所有模塊整合在一起定義一個root,然后我們只有在 main.js 的 root Saga 中調用sagaMiddleware.run。就可以啟動所有的sagas。

對于其他更加詳細的redux-saga學習可以參考文檔

原文鏈接:https://juejin.cn/post/7140109388525617188

欄目分類
最近更新