如果你在用 redux
的話,那你估計也會用到 redux-thunk
這個庫。區別於作者的說法,以下是日了狗的想法。
thunk
求值
日了狗說:“你可能得先了解下
thunk
。”
程式語言早期,計算機學家在研究如何編譯時,對“求值策略”產生了爭執。舉個栗子:
const x = 1;
function foo(num) {
return num * 2;
}
foo(x + 3);
複製程式碼
x + 3
該在何時計算?引發了兩種意見:
- 傳值呼叫,在進入
foo
之間計算,等同於foo(4)
- 傳名呼叫,只在用到時求值,等同於
(x + 3) * 2
兩種計算方式各有利弊,傳值呼叫相對簡單,但有可能根本沒用到,比如:
function bar(a, b) {
return b + 2;
}
bar(1 + 2 * (3 - x) / x -4, x);
複製程式碼
定義的 bar
函式體內沒有用到 a
,根本無需計算,傳值呼叫可能會造成效能損失。所以有些計算機學家更傾向於傳名呼叫
thunk
求值就是基於傳名呼叫的編譯器實現。
const a = 1;
const thunk = function thunk(x) {
return x + 3;
}
function foo(thunk) {
return thunk() * 2;
}
複製程式碼
在 JavaScript 語言中,thunk
函式略有不同。也叫函式“柯里化”,一種函數語言程式設計的概念,將多引數轉化為單引數形式
function foo(var1, var2) {
return var1 + var2;
}
function thunk(var1) {
return function (var2) {
return var1 + var2;
};
}
// 看最多的是 ES6 的騷寫法
const thunk = var1 => var2 => var1 + var2;
// 實際上還是傳值呼叫
foo(1, 2) === thunk(1)(2);
複製程式碼
日了狗說:“好的。我們現在開始回到
redux-thunk
。”
為什麼要用
當你在 redux
中觸發 state
變化時,你肯定會這樣 dispatch(action)
。處理同步程式碼當然沒問題,但是非同步程式碼呢?比如說非同步請求,emmmmm...跟著官方的程式碼走是這樣的:
function success(data) {
return {
type: 'SUCCESS',
data,
};
}
function asyncFunc(payload) {
return dispatch => {
fetch(url, payload)
.then(res => res.json())
.then(data => dispatch(success(data)))
};
}
dispatch(asyncFunc({}));
複製程式碼
如果你沒用 redux-thunk
的話,你應該會收到一條錯誤提示。這是因為 redux
裡做了對 action
的校驗:
// 必須要是個包含 type 屬性的純物件
// 而 asyncFunc 返回的是個 Promise
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
複製程式碼
聰明的你會想到另一種解決辦法。執行 Promise,dispatch
物件:
// store.js
const store = createStore();
// action.js
function success(data) {
return {
type: 'SUCCESS',
data,
};
}
// index.js
fetch(url, data)
.then()
.then(res => /* import store from store.js */ store.dispatch(success(res)));
複製程式碼
福兮禍所依,這樣子的寫法有幾個問題:
- 寫法不規範。
createAction
不統一 - 保持對
store
的引用。汙染全域性、修改此物件都可能造成影響 - 重複程式碼。同一個請求多處寫
日了狗說:“ok,現在我們來看看
redux-thunk
做了什麼事。”
做了什麼事
直接看原始碼。只有區區不到 15 行的程式碼:
// redux-thunk 功能程式碼
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
複製程式碼
光看這裡程式碼一臉懵。可能大家一直在糾結 next
是什麼,action
又是什麼。那不妨多糾結一下,看看 redux 的中介軟體怎麼應用的:
// 核心程式碼
function applyMiddleware(...middlewares) {
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
}
複製程式碼
主要做了兩件事:
- 將
dispatch
注入中介軟體 - 將所有的中介軟體 compose 成一個
dispatch
redux 中的 compose
函式很有意思,將多個函式整合成一個,按順序同步執行,上一個函式的返回值為下一個函式的入參。詳情可看原始碼
Tip: 如果看原始碼不容易理解的話,可以嘗試看下測試用例
瞭解了 applyMiddleware
後,再應用 redux-thunk
,你就會發現原本的 dispatch
函式被改變了。在 action
觸發之間會先執行一遍 redux-thunk
功能程式碼,判斷傳入的 action
是否為函式型別,如果是,則注入 dispatch
,執行此函式
簡而言之,redux-thunk
直接讓 dispatch
跳過了 action
型別驗證,然後就沒啥了。。。
最後,日了狗覺得事情並不簡單。