本文轉載自【微信公眾號:趣談前端,ID:beautifulFront】經微信公眾號授權轉載,如需轉載與原文作者聯繫
日益忙碌的一周又過去了,是時候開始每周一次的總結復盤了,今天筆者就來剖析一下github中star數15.1k的開源項目redux-thunk。
作為一名React方向的前端工程師,不管是被面試還是面試別人,大部分都會說起redux-thunk的實現原理,因為它非常經典且有用,而且代碼量少的感人,只有短短12行代碼,卻能解決React開發中同一個函數支持多dispatch和異步action的問題(雖然這完全依賴於redux的中間件機制(Middleware))。
接下來筆者將從:
Redux的工作機制中間件實現原理redux-thunk源碼實現這三個方面來帶大家徹底掌握redux-thunk源碼,從而對redux有更深入的了解和應用。
正文
在解讀Redux-thunk源碼之前我們需要先掌握redux的基本工作機制和中間件實現原理,這樣才能更好地理解源碼背後的奧義。長話短說我們先來看看redux的幾個核心api及其作用:
redux解決的真正問題是React組件間的狀態共享和狀態管理問題,通過以上的6個核心api我們便能管理複雜的狀態,並能監聽和追溯狀態的改動。機制筆者總結如下:
redux工作機理基本了解之後,我們先看看一個實際的例子:
import actionType from './actionType'class Actions {static start() { return { type: actionType.CREATE_TODO_DOING } } static ok(data, cb) { cb && 'function' === typeof cb && cb(data); return { type: actionType.CREATE_TODO_SUCCESS, payload: data } } static fail(data, cb) { cb && 'function' === typeof cb && cb(data); return { type: actionType.CREATE_TODO_FAILURE, payload: data } }}
以上代碼我們可以發現我們用了一個統一的createAction來創建action,在調用時只需要執行Actions.start()即可,我們也知道action返回的是一個標準的對象,但我們可以在return之前做一些side effect。這裡我們並不能在action中處理異步邏輯,這也是redux-thunk的價值之一,即解決異步調用action。
到這一步我們仍然不能直接進入redux-thunk的源碼分析,因為我們還是不清楚如何解決上述步驟,因為我們還沒有了解redux的中間件機制。
redux中間件機制
說到中間件(middleware),使用過nodejs的人可能會很熟悉,比如說知名的koa中間件,express中間件等,其實中間件筆者的理解是在某個執行流中的某個環節做一些額外的處理的模塊。實現中間件的機制也很簡單, 就是在框架核心執行流中去遍歷外部傳入的中間件,並依次執行即可,我們先來看看redux中如何使用中間件的:
import { createStore, applyMiddleware } from 'redux';import reducers from './reducers';const middlewares = applyMiddleware(middleware1, middleware2);const store = createStore(reducers, middlewares);
所以說redux-thunk是被傳入applyMiddleware方法中作為參數使用的,不難猜到applyMiddleware方法中一定有遍歷執行參數的邏輯,我們來看看applyMiddleware的核心源碼:
export default function applyMiddleware(……middlewares) {return function enhancer(createStore) { return function enhancedCreateStore(……args) { const store = createStore(……args) let dispatch = () => { thrownewError('此處省略n個字……') } const middlewareAPI = { getState: store.getState, dispatch: (……args) => dispatch(……args) } const chain = middlewares.map(function(middleware) { return middleware(middlewareAPI) }) dispatch = compose(……chain)(store.dispatch) return { ……store, dispatch, } } }}
由上面的源碼可知,在chain這段代碼裡我們發現其存儲的是applyMiddleware方法參數傳入getState,dispatch後的調用結果。接下來在dispatch這段代碼中出現了compose函數, 熟悉函數式編程的朋友不難猜到其內部肯定是實現批處理chain的函數,並將store.dispatch泵送至其內部。上面源碼分析後我們知道每一次執行dispatch時,都會先經過middleware的「洗禮」。
我們再來看看compose函數的內部實現:
export default function compose(……funcs) {if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce(function(a, b) { return function (……args) { return a(b(……args)) } })}
由上面代碼可以看出compose最終返回的是一個函數,如果參數大於一時,我們採用reduce將上一個函數返回的結果傳給下一個函數參數,以此來實現之間的參數共享和傳遞,非常經典的設計。
在掌握了redux中間件實現原理之後, 我們再來看redux-thunk源碼就非常容易理解了。
redux-thunk源碼分析
我們先看看這個github中star數15.1k的源碼長啥子:
function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); };}const thunk = createThunkMiddleware();thunk.withExtraArgument = createThunkMiddleware;export default thunk;
沒錯, 這就是redux-thunk的全部源碼了,是不是很nice~。在上面的介紹中我們了解到redux中間件機制使得我們可以在中間件中拿到必備的dispatch, getState,並且在執行之前已經調用了兩層middleware,此時我們可以解剖一下createThunkMiddleware,在第一次調用createThunkMiddleware是在chain階段,即上面源碼分析的:
所以這裡的next也就是第二次調用時的store.dispatch, 為了實現同一函數內能執行多次dispatch,我們會判斷如果action為函數,則執行action本身並把必要參數傳遞給它,否則則直接觸發dispatch,這樣我們就實現了支持action為函數並且支持異步多dispatch的功能了,讀到這還是非常感嘆其設計的優雅和簡潔,不經讓筆者感嘆:學好函數式,走遍天下都不怕!