前端小誌(轉型中)

一個用來記錄人老會忘記的地方

Redux Middleware應用

2017年08月14日
學習SPA一陣子了,有權限的系統還是蠻麻煩的。這次考慮的是Oauth與REST api使用token的問題。

原本一開始的想法是在router執行action的時候(LOCATION_CHANGE),做一個Middleware,執行權限的判斷,但想想有一些頁面是不需要判斷的(Ex:忘記密碼、註冊) 最後決定使用HOC的方式只在特定的router location附加此功能。

不過這些都是對於Oauth還不熟的時候,時間急迫下直接動工寫的,寫這篇文章的時候特別去找了相關文章看看別人是怎麼做的。這篇實作就跟我的想法蠻類似的。

目前手上開發的是Oauth2.0 implicit flow,在毎一次呼叫api的時候都須要在header帶上token,這時候有人告訴我說呼叫api error要跳錯,但是token過期與驗證失敗要導到login畫面去。

最直覺的做法就是每個api的fetch裡面都加入error handler

fetch(api).then( (response) => {
  ...
  if (resoponse.status !== ...) {
    // error handler here
    // dispatch delete token
  }
})

但是最直覺的做法怎樣想都是一個很蠢的寫法XD,更好的寫法應該會想到放到Middleware裡面,對api制定統一格式。強者我同事馬上教我寫fetch middleware。

redux middleware

有時候我們會想在dispatch action -> reducer中間做一些客制的"動作",middleware可以很好的幫我們處理這件事情。 middleware使用的是組合(compose)的方式,所以執行會有順序性。

middleware01

applyMiddleware(
  middleware1,
  middleware2,
  middleware3, // <--這行會先被執行
)

然後middleware的寫法屬於Monkeypatching的curry,寫起來大概長這樣

const middleware = store => next => action => {
  if (action.whatever === distinct value) {
    custom event
  } else {
    return next(action);
  }
}

需要注意store.dispatch與next的效果是不一樣的。 middleware02 網路上有很多好文章,可以去閱讀這篇或是這篇

實作fetch middleware

其實npm上已經有相關的套件了,像是redux-middleware-fetchredux-composable-fetch

我的實作很簡單,帶給action types屬性,分別代表[success, error, loading]時候的action,剩下的我直接帶入fetch的url && settings,應該拿到的resoponseHttpCode,還有帶入callback,方便做一般的處理 或是再chain promise or dispatch皆可以。另外我習慣return promise,這樣前端可以await dispatch,可以更靈活的使用。

//middleware
export default () => next => (action) => {
  if (!action.types) {
    return next(action);
  }
  const [Success, Fail, Request] = action.types;
  const options = action.options;
  next({
    type: Request,
  });
  return new Promise((resolve) => {
    fetch(options.url, options.config).then(async (response) => {
      let json;
      if (response.status !== 204) {
        json = await response.json();
      }
      if (!options.responseCode.includes(response.status)) {
        throw json;
      }
      if (typeof options.onSuccess === 'function') {
        options.onSuccess(json);
      }
      next({
        type: Success,
        data: json,
      });
      resolve(true);
    }).catch((e) => {
      switch(e) {
        case 'token失效':
        case 'api error':
        ...
      }
      resolve(false);
      next({
        type: Fail,
        error: e,
      });
    });
  });
};


//action
export const action = () => ({
  types:[fetchSuccess, fetchError, fetching],
  options: {
    url: apiURL,
    config: {
      method: 'GET',
      ...
    }
  }

})

相信大部份人學習redux要處理非同步一定都聽過redux-thunk,短短的14行code拿下了6000+的星星。

這些都算是middleware的基礎,進階的應用也有chain promise(redux saga,redux observable)或是串web socket,middleware的概念很有趣,實作起來並不能難,能做的事情卻很多,建議一定要學一下。


展開Disqus
分類
最近文章
友站連結
© 2019 Ernie Yang