理解 Redux 的中間件


將該思想抽象出來,其實和 Redux 就無關了。問題變成,怎樣實現在截獲函數的執行,以在其執行前后添加自己的邏輯。

為了演示,我們准備如下的示例代碼來模擬 Redux dispatch action 的場景:

const store = {
  dispatch: action => {
    console.log("dispating action:", action);
  }
};

store.dispatch({ type: "FOO" });
store.dispatch({ type: "BAR" });

我們最終需要實現的效果是 Redux 中 applyMiddleware(...middlewares) 的效果,接收一個中間件數據(函數數組),執行真正的 dispatch 前順次執行這些中間件。

以打日志為例,我們想在調用 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.disatch 而需要使用封裝后的 dispatchWithLog

- 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(</span>error while dispatching <span class="pl-s1"><span class="pl-pse">${</span><span class="pl-smi">action</span>.<span class="pl-c1">type</span><span class="pl-pse">}</span></span><span class="pl-pds">);
}
};

所以針對單個功能的中間件,我們可以提取出其大概的樣子來了:

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(</span>error while dispatching <span class="pl-s1"><span class="pl-pse">${</span><span class="pl-smi">action</span>.<span class="pl-c1">type</span><span class="pl-pse">}</span></span><span class="pl-pds">);
}
};
}

然后按需要應用上述中間件即可:

log(store);
report(store);

上面中間件的調用可專門編寫一個方法來做:

function applyMiddlewares(store, middlewares) {
  middlewares.forEach(middleware => middleware(store));
}

隱藏 Monkeypatching

真實場景下,各中間件由三方編寫,如果每個中間件都直接去篡改 store.dispatch 不太科學也不安全。如此的話,中間件只需要關注新添加的邏輯,將新的 dispatch 返回即可,由框架層面拿到這些中間件后逐個調用並重寫原來的 dispatch,將篡改的操作收斂。

所以中間件的模式更新成如下:

function middleware(store) {
  const next = store.dispatch;
-  store.dispatch = function(action) {
+  return function(action) {
    // 中間件中其他邏輯
    next(action);
    // 中間件中其他邏輯
  };
}

改寫 logreport 中間件:

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

更新 applyMiddlewares 方法:

function applyMiddlewares(store, middlewares) {
  middlewares.forEach(middleware => {
    store.dispatch = middleware(store);
  });
}

最后,應用中間件:

applyMiddlewares(store, [log, report]);

進一步優化

之所以在應用中間件過程中每次都重新給 store.dispatch 賦值,是想讓后續中間件在通過 store.dispatch 訪問時,能夠拿到前面中間件修改過的 dispatch 函數。

如果中間件中不是直接從 store 身上去獲取 store.dispatch,而是前面已經執行過的中間件將新的 dispatch 傳遞給中間件,則可以避免每次對 store.dispatch 的賦值。

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

相關資源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM