Redux API 總覽
淺談redux 中間件的原理
在 Redux 的源碼目錄 src/,我們可以看到如下文件結構:
├── utils/
│ ├── warning.js # 打醬油的,負責在控制臺顯示警告信息
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js # 入口文件
除去打醬油的 utils/warning.js 以及入口文件 index.js,剩下那 5 個就是 Redux 的 API
compose(...functions)
先說這個 API 的原因是它沒有依賴,是一個純函數(shù)
⊙ 源碼分析
/**
* 看起來逼格很高,實際運用其實是這樣子的:
* compose(f, g, h)(...arg) => f(g(h(...args)))
*
* 值得注意的是,它用到了 reduceRight,因此執(zhí)行順序是從右到左
*
* @param {多個函數(shù),用逗號隔開}
* @return {函數(shù)}
*/
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
---
compose(fn1,fn2,fn3)(0)

這里的關鍵點在于,reduceRight 可傳入初始值:
// 由于 reduce / reduceRight 僅僅是方向的不同,因此下面用 reduce 說明即可
var arr = [1, 2, 3, 4, 5]
var re1 = arr.reduce(function(total, i) {
return total + i
})
console.log(re1) // 15
var re2 = arr.reduce(function(total, i) {
return total + i
}, 100) // <---------------傳入一個初始值
console.log(re2) // 115
下面是 compose 的實例(在線演示):
<!DOCTYPE html>
<html>
<head>
<script src="http://cdn.bootcss.com/redux/3.5.2/redux.min.js"></script>
</head>
<body>
<script>
function func1(num) {
console.log('func1 獲得參數(shù) ' + num);
return num + 1;
}
function func2(num) {
console.log('func2 獲得參數(shù) ' + num);
return num + 2;
}
function func3(num) {
console.log('func3 獲得參數(shù) ' + num);
return num + 3;
}
// 有點難看(如果函數(shù)名再長一點,那屏幕就不夠寬了)
var re1 = func3(func2(func1(0)));
console.log('re1:' + re1);
console.log('===============');
// 很優(yōu)雅
var re2 = Redux.compose(func3, func2, func1)(0);
console.log('re2:' + re2);
</script>
</body>
</html>
控制臺輸出:
func1 獲得參數(shù) 0
func2 獲得參數(shù) 1
func3 獲得參數(shù) 3
re1:6
===============
func1 獲得參數(shù) 0
func2 獲得參數(shù) 1
func3 獲得參數(shù) 3
re2:6
Store Enhancer
說白了,Store 增強器就是對生成的 store API 進行改造,這是它與中間件最大的區(qū)別(中間件不修改 store 的 API)
而改造 store 的 API 就要從它的締造者 createStore 入手。例如,Redux 的 API applyMiddleware 就是一個 Store 增強器。
為什么 applyMiddleware 要為 dispatch 創(chuàng)建一個閉包?
applyMiddleware 從 store 中獲取已有的 dispatch,然后把它封裝在一個閉包中來創(chuàng)建最開始的 middleware 鏈。然后用一個對象調(diào)用來調(diào)用,以暴露出 getState 和 dispatch 函數(shù)。這樣做可以使得 middleware 在初始化時可以使用 dispatch。
import compose from './compose' // 這貨的作用其實就是 compose(f, g, h)(action) => f(g(h(action)))
/* 傳入一坨中間件 */
export default function applyMiddleware(...middlewares) {
/* 傳入 createStore */
return function(createStore) {
/* 返回一個函數(shù)簽名跟 createStore 一模一樣的函數(shù),亦即返回的是一個增強版的 createStore */
return function(reducer, preloadedState, enhancer) {
// 用原 createStore 先生成一個 store,其包含 getState / dispatch / subscribe / replaceReducer 四個 API
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch // 指向原 dispatch
var chain = [] // 存儲中間件的數(shù)組
// 提供給中間件的 API(其實都是 store 的 API)
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 給中間件“裝上” API,見上面 ⊙Middleware【降低逼格寫法】的【錨點-1】
chain = middlewares.map(middleware => middleware(middlewareAPI))
//chain數(shù)組里面的數(shù)據(jù)結構是這樣的 :
//[ function(next){return function(action){...}},function(next){return function(action){...}} ...]
// 串聯(lián)所有中間件
// store.dispatch傳給last(...args)
dispatch = compose(...chain)(store.dispatch)
// 例如,chain 為 [M3, M2, M1],而 compose 是從右到左進行“包裹”的
// 那么,M1 的 dispatch 參數(shù)為 store.dispatch(見【降低逼格寫法】的【錨點-2】)
// 往后,M2 的 dispatch 參數(shù)為 M1 的中間件處理邏輯哥(見【降低逼格寫法】的【錨點-3】)
// 同樣,M3 的 dispatch 參數(shù)為 M2 的中間件處理邏輯哥
// 最后,我們得到串聯(lián)后的中間件鏈:M3(M2(M1(store.dispatch)))
//(這種形式的串聯(lián)類似于洋蔥,可參考文末的拓展閱讀:中間件的洋蔥模型)
// 在此衷心感謝 @ibufu 在 issue8 中指出之前我對此處的錯誤解讀
return {
...store, // store 的 API 中保留 getState / subsribe / replaceReducer
dispatch // 新 dispatch 覆蓋原 dispatch,往后調(diào)用 dispatch 就會觸發(fā) chain 內(nèi)的中間件鏈式串聯(lián)執(zhí)行
}
}
}
}
最終返回的雖然還是 store 的那四個 API,但其中的 dispatch 函數(shù)的功能被增強了,這就是所謂的 Store Enhancer
Middleware
使用包含自定義功能的 middleware 來擴展 Redux 是一種推薦的方式。Middleware 可以讓你包裝 store 的 dispatch 方法來達到你想要的目的。同時, middleware 還擁有“可組合”這一關鍵特性。多個 middleware 可以被組合到一起使用,形成 middleware 鏈。其中,每個 middleware 都不需要關心鏈中它前后的 middleware 的任何信息。
Middleware 最常見的使用場景是無需引用大量代碼或依賴類似 Rx 的第三方庫實現(xiàn)異步 actions。這種方式可以讓你像 dispatch 一般的 actions 那樣 dispatch 異步 actions。
例如,redux-thunk 支持 dispatch function,以此讓 action creator 控制反轉。被 dispatch 的 function 會接收 dispatch 作為參數(shù),并且可以異步調(diào)用它。這類的 function 就稱為 thunk。另一個 middleware 的示例是 redux-promise。它支持 dispatch 一個異步的 Promise action,并且在 Promise resolve 后可以 dispatch 一個普通的 action。
Middleware 并不需要和 createStore 綁在一起使用,也不是 Redux 架構的基礎組成部分,但它帶來的益處讓我們認為有必要在 Redux 核心中包含對它的支持。因此,雖然不同的 middleware 可能在易用性和用法上有所不同,它仍被作為擴展 dispatch 的唯一標準的方式。
說白了,Redux 引入中間件機制,其實就是為了在 dispatch 前后,統(tǒng)一“做愛做的事”。。。
諸如統(tǒng)一的日志記錄、引入 thunk 統(tǒng)一處理異步 Action Creator 等都屬于中間件
下面是一個簡單的打印動作前后 state 的中間件:
讓 middleware 以方法參數(shù)的形式接收一個 next() 方法,而不是通過 store 的實例去獲取。
/* 裝逼寫法 */
const printStateMiddleware = ({ getState }) => next => action => {
console.log('state before dispatch', getState())
let returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
-------------------------------------------------
/* 降低逼格寫法 */
function printStateMiddleware(middlewareAPI) { // 記為【錨點-1】,中間件內(nèi)可用的 API
return function (dispatch) { // 記為【錨點-2】,傳入上級中間件處理邏輯(若無則為原 store.dispatch)
// 下面記為【錨點-3】,整個函數(shù)將會被傳到下級中間件(如果有的話)作為它的 dispatch 參數(shù)
return function (action) { // <---------------------------------------------- 這貨就叫做【中間件處理邏輯哥】吧
console.log('state before dispatch', middlewareAPI.getState())
var returnValue = dispatch(action) // 還記得嗎,dispatch 的返回值其實還是 action
console.log('state after dispatch', middlewareAPI.getState())
return returnValue // 將 action 返回給上一個中間件(實際上可以返回任意值,或不返回)
// 在此衷心感謝 @zaleGZL 在 issue15 中指出之前我對此處的錯誤解讀
}
}
}
======
// 簡單寫法,主要看流程
function middleware({dispatch, getState}) {
return function (next) {
return function (action) {
return next(action);
}
}
}
Middleware 接收了一個 next() 的 dispatch 函數(shù),并返回一個 dispatch 函數(shù),返回的函數(shù)會被作為下一個 middleware 的 next(),以此類推。由于 store 中類似 getState() 的方法依舊非常有用,我們將 store 作為頂層的參數(shù),使得它可以在所有 middleware 中被使用。
參數(shù)
-
...middleware(arguments): 遵循 Redux middleware API 的函數(shù)。每個 middleware 接受Store的dispatch和getState函數(shù)作為命名參數(shù),并返回一個函數(shù)。該函數(shù)會被傳入 被稱為next的下一個 middleware 的 dispatch 方法,并返回一個接收 action 的新函數(shù),這個函數(shù)可以直接調(diào)用next(action),或者在其他需要的時刻調(diào)用,甚至根本不去調(diào)用它。調(diào)用鏈中最后一個 middleware 會接受真實的 store 的dispatch方法作為next參數(shù),并借此結束調(diào)用鏈。所以,middleware 的函數(shù)簽名是({ getState, dispatch }) => next => action。
返回值
(Function) 一個應用了 middleware 后的 store enhancer。這個 store enhancer 的簽名是 createStore => createStore,但是最簡單的使用方法就是直接作為最后一個 enhancer 參數(shù)傳遞給 createStore() 函數(shù)。
示例: 自定義 Logger Middleware
import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'
function logger({ getState }) {
return next => action => {
console.log('will dispatch', action)
// 調(diào)用 middleware 鏈中下一個 middleware 的 dispatch。
let returnValue = next(action)
console.log('state after dispatch', getState())
// 一般會是 action 本身,除非
// 后面的 middleware 修改了它。
return returnValue
}
}
let store = createStore(
todos,
['Use Redux'],
applyMiddleware(logger)
)
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
// (將打印如下信息:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]
整個流程梳理
簡單版本
看以下代碼就知道大體流程了。
說白了,Store 增強器就是對生成的 store API 進行改造,這是它與中間件最大的區(qū)別(中間件不修改 store 的 API)
而改造 store 的 API 就要從它的締造者 createStore 入手。例如,Redux 的 API applyMiddleware 就是一個 Store 增強器:
export default function applyMiddleware(...middlewares) {
//返回一個函數(shù)簽名跟 createStore 一模一樣的函數(shù)
//亦即返回的是一個增強版的 createStore
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
// 提供給中間件的 API(其實都是 store 的 API)
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 給中間件“裝上” API
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
// 例如,chain 為 [M3, M2, M1],而 compose 是從右到左進行“包裹”的
// 那么,M1 的 dispatch 參數(shù)為 store.dispatch(見【降低逼格寫法】的【錨點-2】)
// 往后,M2 的 dispatch 參數(shù)為 M1 的中間件處理邏輯哥(見【降低逼格寫法】的【錨點-3】)
// 同樣,M3 的 dispatch 參數(shù)為 M2 的中間件處理邏輯哥
// 最后,我們得到串聯(lián)后的中間件鏈:M3(M2(M1(store.dispatch)))
//(這種形式的串聯(lián)類似于洋蔥,可參考文末的拓展閱讀:中間件的洋蔥模型)
return {
...store,
dispatch
}
}
}
export function createStore (reducer,enhancer) {
// 如果傳入了applymiddleweare,把createStore包裝
if(enhancer){
return enhancer(createStore)(reducer)
}
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
//reducer只是返回對象,所以需要用每次 reducer(state, action) 的調(diào)用結果覆蓋原來的 state
state = reducer(state, action)
listeners.forEach((listener) => listener())
return action
// 為了方便鏈式調(diào)用,dispatch 執(zhí)行完畢后,返回 action
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
將 middleware 串連起來的必要性是顯而易見的。
如果 applyMiddlewareByMonkeypatching 方法中沒有在第一個 middleware 執(zhí)行時立即替換掉 store.dispatch,那么 store.dispatch 將會一直指向原始的 dispatch 方法。也就是說,第二個 middleware 依舊會作用在原始的 dispatch 方法。
但是,還有另一種方式來實現(xiàn)這種鏈式調(diào)用的效果。可以讓 middleware 以方法參數(shù)的形式接收一個 next() 方法,而不是通過 store 的實例去獲取。
action:第三個參數(shù)備注一下
function readMsg(from){
// getState,獲取redux內(nèi)已經(jīng)登錄的信息
return (dispatch,getState)=>{
axios.post('/user/readmsg',{from}).
then(res=>{
// 獲取當前用戶ID
const userid = getState().user._id
if(res.status == 200 && res.data.code == 0){
dispatch(msgRead({userid,from,num:res.data.num}))
}
})
}
}
===================
const arrayThunk = ({dispatch,getState})=>next=>action=>{
// 如果是函數(shù),執(zhí)行以下,參數(shù)是dispatch和getState
//一下是多個中間件的情況
if(Array.isArray(action)){
action.forEach(v=>next(v))
}
if(typeof action == 'function'){
return action(dispatch,getState)
}
// 默認,什么都沒干
return next(action)
}
=====
/* 降低逼格寫法 */
function printStateMiddleware(middlewareAPI) { // 記為【錨點-1】,中間件內(nèi)可用的 API
return function (dispatch) { // 記為【錨點-2】,傳入上級中間件處理邏輯(若無則為原 store.dispatch)
// 下面記為【錨點-3】,整個函數(shù)將會被傳到下級中間件(如果有的話)作為它的 dispatch 參數(shù)
return function (action) { // 這貨就叫做【中間件處理邏輯哥】吧
console.log('state before dispatch', middlewareAPI.getState())
var returnValue = dispatch(action) // 還記得嗎,dispatch 的返回值其實還是 action
console.log('state after dispatch', middlewareAPI.getState())
return returnValue // 將 action 返回給上一個中間件(實際上可以返回任意值,或不返回)
}
}
}
Redux 有五個 API,分別是:
- createStore(reducer, [initialState])
- combineReducers(reducers)
- applyMiddleware(...middlewares)
- bindActionCreators(actionCreators, dispatch)
- compose(...functions)
createStore 生成的 store 有四個 API,分別是:
- getState()
- dispatch(action)
- subscribe(listener)
- replaceReducer(nextReducer)