Redux的中間件(Middleware)遵循了即插即用的設計思想,出現在Action到達Reducer之前(如圖10所示)的位置。中間件是一個固定模式的獨立函數,當把多個中間件像管道那樣串聯在一起時,前一個中間件不但能將其輸出傳給下一個中間件作為輸入,還能中斷整條管道。在引入中間件后,既能擴展Redux的功能,也能增強dispatch()函數,適應不同的業務需求,例如通過中間件記錄日志、報告奔潰或處理異步請求等。
:-: 
圖10 中間件管道
## 一、開發模式
  在設計中間件函數時,會遵循一個固定的模式,如下代碼所示,使用了柯里化、高階函數等函數式編程中的概念。
~~~js
function middleware(store) {
return function(next) {
return function(action) {
return next(action);
};
};
}
~~~
  middleware()函數接收一個Store實例,返回值是一個接收next參數的函數。其中next也是一個函數,用來將控制權轉移給下一個中間件,從而實現了中間件之間的串聯,它會返回一個處理Action對象的函數。由于閉包的作用,在這最內層的函數中,依然能調用外層的對象和函數,例如訪問action所攜帶的數據、執行store中的dispatch()或getState()方法等。示例中的middleware()函數只是單純的將接收到的action對象轉交給后面的中間件,沒有對其做額外的處理。
  之所以將中間件函數寫成層層嵌套的模式,有以下幾個原因。
  (1)擴展Redux需要遵循函數式編程的設計思想。
  (2)柯里化讓中間件更容易處理數據流,即便于形成數據處理管道。
  (3)每個中間件的職責單一(即函數代碼盡量少),通過嵌套組合完成復雜功能。
  利用ES6中的箭頭函數能將middleware()函數改寫得更加簡潔,如下所示。
~~~js
const middleware = store => next => action => {
return next(action);
};
~~~
## 二、applyMiddleware()
  Redux提供了組織中間件的applyMiddleware()函數,在閱讀過此函數的源碼(如下所示)之后,才能更好的理解中間件的開發模式。
~~~js
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = () => {
throw new Error(
"Dispatching while constructing your middleware is not allowed. " +
"Other middleware would not be applied to this dispatch."
);
};
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return { ...store, dispatch };
};
}
~~~
**1)源碼分析**
  接下來會分析函數中的代碼,為了便于理解,在說明中還會給出相應的語句。
  (1)利用剩余參數(...middlewares)的方式,applyMiddleware()函數可以接收任意多個中間件。
  (2)返回一個接收createStore參數的函數,在函數體中調用Redux的createStore()函數,得到一個store實例。
~~~js
const store = createStore(...args);
~~~
  (3)middlewareAPI變量包含了中間件需要的參數,即store.getState()和dispatch()方法。
~~~js
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
};
~~~
  (4)將middlewareAPI變量傳遞給每個中間件并調用一次,再將返回的函數組成一個新數組。
~~~js
const chain = middlewares.map(middleware => middleware(middlewareAPI));
~~~
  (5)通過compose()增強dispatch()方法,compose()是Redux內置的函數,可從右向左合成多個函數,例如compose(f1, f2, f3)(arg)相當于f1(f2(f3(arg)))。
~~~js
dispatch = compose(...chain)(store.dispatch);
~~~
  (6)最終得到一個對象,包含store實例的方法和增強后的dispatch()方法。
~~~js
return { ...store, dispatch };
~~~
**2)使用方式**
  applyMiddleware()函數有兩種使用方式,下面用一個例子來演示,先定義兩個中間件:m1和m2,以及一個Reducer函數:caculate()。
~~~js
const m1 = store => next => action => {
console.log("m1");
return next(action);
};
const m2 = store => next => action => {
console.log("m2");
return next(action);
};
function caculate(previousState = {digit:0}, action) {
let state = Object.assign({}, previousState);
switch (action.type) {
case "ADD":
state.digit += 1;
break;
case "MINUS":
state.digit -= 1;
}
return state;
}
~~~
  (1)applyMiddleware()是一個三級柯里化的函數,如果要使用,那么可以像下面這樣調用,先傳兩個中間件,再傳createStore()函數,最后傳caculate()函數。
~~~js
let store = applyMiddleware(m1, m2)(createStore)(caculate);
~~~
  (2)applyMiddleware()函數還可以作為createStore()的最后一個參數,如下代碼所示,第一個參數是caculate()函數,第二個參數是applyMiddleware()函數的結果。
~~~js
let store = createStore(caculate, applyMiddleware(m1, m2));
~~~
  在applyMiddleware()函數的內部,chain數組的值是\[m1(next), m2(next)\],由m1和m2返回的包含next參數的函數組成。增強后的dispatch()方法通過m1(m2(store.dispatch))得到,具體如下所示。
~~~js
dispatch = action => {
console.log("m1");
return next(action);
};
~~~
  當調用store實例的dispatch()方法(如下代碼所示)時,會先輸出“m1”;然后調用next()函數,也就是m2(next),輸出“m2”;最后調用m2中的next()函數,也就是dispatch()方法。注意,不能在中間件中直接調用dispatch()方法,以免造成死循環。
~~~js
store.dispatch({ type: "ADD" });
~~~
## 三、redux-thunk
  如果要在Redux中處理異步請求,那么可以借助中間件實現,目前市面上已有很多封裝好的中間件可供使用,例如redux-thunk、redux-promise或redux-saga等。本節將著重講解redux-thunk中間件,其核心代碼如下所示。
~~~js
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === "function") {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
~~~
  首先檢測action的類型,如果是函數,那么就直接調用并將dispatch、getState和extraArgument作為參數傳入;否則就調用next參數,轉移控制權。redux-thunk其實擴展了dispatch()方法,使其參數既可以是JavaScript對象,也可以是函數。
  接下來用一個簡單的例子演示redux-thunk的用法,如下代碼所示,首先通過import引入redux-thunk,并定義一個異步Action。
~~~js
import thunk from "redux-thunk";
function asynAction() {
return dispatch => {
fetch("server.php").then(
response => response.json(),
error => console.log(error)
).then(data => {
dispatch(data); //{type: "ADD"}
});
};
}
~~~
  然后讓asynAction()函數返回一個接收dispatch參數的函數,在函數體內通過fetch()函數請求服務端的資源,再利用得到的Promise處理獲取到的數據。在第二個then()方法中,data參數被賦為{type: "ADD"},也就是server.php響應的數據,其代碼如下所示。
~~~php
<?php
$json = [
'type' => 'ADD'
];
echo json_encode($json);
~~~
  再接入redux-thunk中間件,即將thunk傳給applyMiddleware()函數,最后發送一個異步Action,如下所示。
~~~js
let store = createStore(caculate, applyMiddleware(thunk));
store.dispatch(asynAction());
~~~
*****
> 原文出處:
[博客園-React躬行記](https://www.cnblogs.com/strick/category/1455720.html)
[知乎專欄-React躬行記](https://zhuanlan.zhihu.com/pwreact)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020