前言
最近剛剛完成了畢業答辯,我的畢設內容是基於React系列技術棧開發的一個類似Instagram的Web App,戳此看看。開發完後,我驚奇的發現:咦,之前就聽說有個叫做redux-thunk的東西,我怎麼沒用到?業務場景太簡單了?於是大概研究了下。。
概念的基本介紹
關於redux-thunk的基本介紹,也許你可以先看看stackoverflow上面的介紹。個人理解:redux-thunk改寫了dispatch API,使其具備接受一個函式作為引數的能力,從而達到middleware的效果,即在redux的dispatch action => reducer => store這個流程中,在action 被髮起之後,到達 reducer 之前的擴充套件點,加入相關操作,比如發生請求、log資訊等。
實際使用
以我本次畢設專案中,在redux流程中加入非同步請求為例,為動態點贊功能部分程式碼實現:
- 本來我是這樣寫的
//from action.js
const LIKE = (id) => ({
type: "LIKE",
id:id
})
reqLike({id:id}).then(res =>{ dispatch(LIKE(id))})
複製程式碼
可以看到,我在請求以後的回撥函式中dispatch action去同步redux store中的狀態。
- 加入redux-thunk之後我是這樣寫的:
//from action.js
const LIKE = (id) => {
return function (dispatch,getState) {
reqLike({id:id}).then(res =>{
dispatch({
type: "LIKE",
id:id
})
})
}
}
dispatch(LIKE(id))
複製程式碼
改變以後,從功能層面上來說,兩者並無差別,都可以滿足業務場景需求。但除此之外我們可以發現:
- 1.dispatch接受的引數由一個PlainObject變為一個函式
- 2.我們把請求的非同步操作從dispatch action這個redux流程外塞到的流程裡,這看起來將非同步操作內聚到這個流程中,無論是從邏輯上理解(這很middleware!)還是專案程式碼開發維護(區分非同步與同步狀態管理流程進行維護管理)上都是很大的改進
- 3.如果專案中有多處需要實現點贊功能,我們可以節省很多冗餘程式碼,不用到處在dispatch外層套上reqLike(id).then(…)
原始碼解析
瞭解了redux-thunk的基本概念以及應用後,我們一起看看原始碼加深下理解吧,原始碼十分精巧。
首先看到redux原始碼中applyMiddleware的部分,我們將thunk作為引數傳入之後,直接返回了一個函式,這個函式作為enhancer傳入redux原始碼中的createStore函式中。
export default function applyMiddleware(...middlewares) {
//這個返回函式就是enhancer
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製程式碼
在redux原始碼中的createStore函式中,enhancer被執行,傳入引數createStore,又緊接著執行其返回的函式,傳入reducer和preloadedState.
if (typeof enhancer !== `undefined`) {
if (typeof enhancer !== `function`) {
throw new Error(`Expected the enhancer to be a function.`)
}
return enhancer(createStore)(reducer, preloadedState)
}
複製程式碼
接下來,我們進入applyMiddleware和thunk的關鍵部分,上面applyMiddleware接受的最初的(…middlewares)引數其實就是thunk,thunk會被執行,並且傳入引數getState和dispatch.
//傳入到thunk的引數
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
//在map中執行thunk
chain = middlewares.map(middleware =>middleware(middlewareAPI))
//重新改寫dispatch
dispatch = compose(...chain)(store.dispatch)
複製程式碼
那麼上面的chain是什麼呢,我們終於可以去看redux-thunk的原始碼了!
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
//這裡返回的函式就是chain
return function (next) {
//這裡返回的函式就是改寫的dispatch
return function (action) {
if (typeof action === `function`) {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
var thunk = createThunkMiddleware();
複製程式碼
從原始碼我們可以看出,chain就是以next作為形參的匿名函式,至於compose只是不斷傳遞每個函式的返回值給下一個執行函式,然後依次去執行它所有傳入的函式而已,它原始碼中的註釋說的很清楚:For example, compose(f, g, h) is identical to doing (…args) => f(g(h(…args))).
我們這裡的chain只是一個函式而已,所以很簡單,就是執行chain,並且傳入store.dispatch作為next就行。
接下來,進入最後一步,改寫了dispatch,最終變為:
function (action) {
if (typeof action === `function`) {
return action(dispatch, getState, extraArgument);
}
//next為之前傳入的store.dispatch,即改寫前的dispatch
return next(action);
};
複製程式碼
如果傳入的引數是函式,則執行函式。否則還是跟之前一樣dispatch(PlainObject).
總結
redux-thunk實現了相關非同步流程內聚到redux的流程中,實現middleware的功能,也便於專案的開發與維護,避免冗餘程式碼。而實現的方式便是改寫redux中的dispatch API,使其可以除PlainObject外,接受一個函式作為引數。