理解 Redux 的中介軟體
將該思想抽象出來,其實和 Redux 就無關了。問題變成,怎樣實現在截獲函式的執行,以在其執行前後新增自己的邏輯。 為了演示,我們準備如下的示例程式碼來模擬 Redux dispatch action 的場景: const store = { dispatch: action => { console.log("dispating action:", action); } }; store.dispatch({ type: "FOO" }); store.dispatch({ type: "BAR" }); 我們最終需要實現的效果是 Redux 中 以打日誌為例,我們想在呼叫 dispatch 時進行日誌輸出。 嘗試1 - 手動直接的做法就是手動進行。 console.log("before dispatch `FOO`"); store.dispatch({ type: "FOO" }); console.log("before dispatch `FOO`"); console.log("before dispatch `BAR`"); store.dispatch({ type: "BAR" }); console.log("before dispatch `BAR`"); 但其實這並不算一個系統的解決方案,至少需要擺脫手動這種方式。 嘗試2 - 包裝既然所有 dispatch 操作都會打日誌,完全有理由抽取一個方法,將 dispatch 進行包裝,在這個方法裡來做這些事情。 function dispatchWithLog(action) { console.log(`before dispatch ${action.type}`); store.dispatch(action); console.log(`after dispatch ${action.type}`); } 但呼叫的地方也得變,不能直接使用原始的 - store.dispatch({ type: "FOO" }); - store.dispatch({ type: "BAR" }); + dispatchWithLog({ type: "FOO" }); + dispatchWithLog({ type: "BAR" }); 嘗試3 - 替換實現/Monkeypatching如果我們直接替換掉原始函式的實現,便可以做到呼叫的地方不受影響而實現新增的 log 功能,雖然修改別人提供的方法容易引起 bug 且不太科學。 const original = store.dispatch; store.dispatch = function log(action) { console.log(`before dispatch ${action.type}`); original(action); console.log(`after dispatch ${action.type}`); }; store.dispatch({ type: "FOO" }); store.dispatch({ type: "BAR" }); 嘗試4 - 多個函式的截獲除了新增 log,如果還想對每次 dispatch 進行錯誤監控,只需要拿到前面已經替換過實現的 dispatch 方法再次進行替換包裝即可。 const original = store.dispatch; store.dispatch = function log(action) { console.log(`before dispatch ${action.type}`); original(action); console.log(`after dispatch ${action.type}`); }; const next = store.dispatch; store.dispatch = function report(action) { console.log("report middleware"); try { next(action); } catch (error) { console.log(`error while dispatching ${action.type}`); } }; 所以針對單個功能的中介軟體,我們可以提取出其大概的樣子來了: function middleware(store) { const next = store.dispatch; store.dispatch = function(action) { // 中介軟體中其他邏輯 next(action); // 中介軟體中其他邏輯 }; } 改寫日誌和錯誤監控為如下: function log(store) { const next = store.dispatch; store.dispatch = function(action) { console.log(`before dispatch ${action.type}`); next(action); console.log(`after dispatch ${action.type}`); }; } function report(store) { const next = store.dispatch; store.dispatch = function(action) { console.log("report middleware"); try { next(action); } catch (error) { console.log(`error while dispatching ${action.type}`); } }; } 然後按需要應用上述中介軟體即可: log(store); report(store); 上面中介軟體的呼叫可專門編寫一個方法來做: function applyMiddlewares(store, middlewares) { middlewares.forEach(middleware => middleware(store)); } 隱藏 Monkeypatching真實場景下,各中介軟體由三方編寫,如果每個中介軟體都直接去篡改 所以中介軟體的模式更新成如下: function middleware(store) { const next = store.dispatch; - store.dispatch = function(action) { + return function(action) { // 中介軟體中其他邏輯 next(action); // 中介軟體中其他邏輯 }; } 改寫 function log(store) { const next = store.dispatch; - store.dispatch = function(action) { + return function(action) { console.log(`before dispatch ${action.type}`); next(action); console.log(`after dispatch ${action.type}`); }; } function report(store) { const next = store.dispatch; - store.dispatch = function(action) { + return function(action) { console.log("report middleware"); try { next(action); } catch (error) { console.log(`error while dispatching ${action.type}`); } }; } 更新 function applyMiddlewares(store, middlewares) { middlewares.forEach(middleware => { store.dispatch = middleware(store); }); } 最後,應用中介軟體: applyMiddlewares(store, [log, report]); 進一步優化之所以在應用中介軟體過程中每次都重新給 如果中介軟體中不是直接從 function applyMiddlewares(store, middlewares) { store.dispatch = middlewares.reduce( (next, middleware) => middleware(next), store.dispatch ); } 忽略掉實際原始碼中的一些差異,以上,大致就是 Redux 中介軟體的建立和應用了。 測試function m1(next) { return function(action) { console.log(`1 start`); next(action); console.log(`1 end`); }; } function m2(next) { return function(action) { console.log(`2 start`); next(action); console.log(`2 end`); }; } function m3(next) { return function(action) { console.log(`3 start`); next(action); console.log(`3 end`); }; applyMiddlewares(store, [m1, m2, m3]); store.dispatch({ type: "FOO" }); store.dispatch({ type: "BAR" }); } 輸出結果: 3 start 2 start 1 start dispating action: { type: 'FOO' } 1 end 2 end 3 end 3 start 2 start 1 start dispating action: { type: 'BAR' } 1 end 2 end 3 end 相關資源
|