原本一開始的想法是在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。
有時候我們會想在dispatch action -> reducer中間做一些客制的"動作",middleware可以很好的幫我們處理這件事情。 middleware使用的是組合(compose)的方式,所以執行會有順序性。
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的效果是不一樣的。 網路上有很多好文章,可以去閱讀這篇或是這篇。
其實npm上已經有相關的套件了,像是redux-middleware-fetch與redux-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的概念很有趣,實作起來並不能難,能做的事情卻很多,建議一定要學一下。