深度剖析Github上15.1k Star項目:Redux-Thunk
日益忙碌的一周又過去了,是時候開始每周一次的總結(jié)復(fù)盤了,今天筆者就來剖析一下github中star數(shù)15.1k的開源項目redux-thunk。
作為一名React方向的前端工程師,不管是被面試還是面試別人,大部分都會說起redux-thunk的實現(xiàn)原理,因為它非常經(jīng)典且有用,而且代碼量少的感人,只有短短12行代碼,卻能解決React開發(fā)中同一個函數(shù)支持多dispatch和異步action的問題(雖然這完全依賴于redux的中間件機制(Middleware))。
接下來筆者將從:
- Redux的工作機制
- 中間件實現(xiàn)原理
- redux-thunk源碼實現(xiàn)
這三個方面來帶大家徹底掌握redux-thunk源碼,從而對redux有更深入的了解和應(yīng)用。如果大家對react-redux-redux-thunk實戰(zhàn)感興趣的,讀完之后可以移步筆者的《徹底掌握redux》之開發(fā)一個任務(wù)管理平臺
正文
在解讀Redux-thunk源碼之前我們需要先掌握redux的基本工作機制和中間件實現(xiàn)原理,這樣才能更好的理解源碼背后的奧義。長話短說我們先來看看redux的幾個核心api及其作用:
redux解決的真正問題是React組件間的狀態(tài)共享和狀態(tài)管理問題,通過以上的6個核心api我們便能管理復(fù)雜的狀態(tài),并能監(jiān)聽和追溯狀態(tài)的改動。機制筆者總結(jié)如下:
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
- }
- }
- }
以上代碼我們可以發(fā)現(xiàn)我們用了一個統(tǒng)一的createAction來創(chuàng)建action,在調(diào)用時只需要執(zhí)行Actions.start()即可,我們也知道action返回的是一個標準的對象,但我們可以在return之前做一些side effect。這里我們并不能在action中處理異步邏輯,這也是redux-thunk的價值之一,即解決異步調(diào)用action。
到這一步我們?nèi)匀徊荒苤苯舆M入redux-thunk的源碼分析,因為我們還是不清楚如何解決上述步驟,因為我們還沒有了解redux的中間件機制。
redux中間件機制
說到中間件(middleware),使用過nodejs的人可能會很熟悉,比如說知名的koa中間件,express中間件等,其實中間件筆者的理解是在某個執(zhí)行流中的某個環(huán)節(jié)做一些額外的處理的模塊。實現(xiàn)中間件的機制也很簡單, 就是在框架核心執(zhí)行流中去遍歷外部傳入的中間件,并依次執(zhí)行即可,我們先來看看redux中如何使用中間件的:
- import { createStore, applyMiddleware } from 'redux';
- import reducers from './reducers';
- const middlewares = applyMiddleware(middleware1, middleware2);
- const store = createStore(reducers, middlewares);
所以說redux-thunk是被傳入applyMiddleware方法中作為參數(shù)使用的,不難猜到applyMiddleware方法中一定有遍歷執(zhí)行參數(shù)的邏輯,我們來看看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這段代碼里我們發(fā)現(xiàn)其存儲的是applyMiddleware方法參數(shù)傳入getState,dispatch后的調(diào)用結(jié)果。接下來在dispatch這段代碼中出現(xiàn)了compose函數(shù), 熟悉函數(shù)式編程的朋友不難猜到其內(nèi)部肯定是實現(xiàn)批處理chain的函數(shù),并將store.dispatch泵送至其內(nèi)部。上面源碼分析后我們知道每一次執(zhí)行dispatch時,都會先經(jīng)過middleware的“洗禮”。
我們再來看看compose函數(shù)的內(nèi)部實現(xiàn):
- 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最終返回的是一個函數(shù),如果參數(shù)大于一時,我們采用reduce將上一個函數(shù)返回的結(jié)果傳給下一個函數(shù)參數(shù),以此來實現(xiàn)之間的參數(shù)共享和傳遞,非常經(jīng)典的設(shè)計。
在掌握了redux中間件實現(xiàn)原理之后, 我們再來看redux-thunk源碼就非常容易理解了。
redux-thunk源碼分析
我們先看看這個github中star數(shù)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,并且在執(zhí)行之前已經(jīng)調(diào)用了兩層middleware,此時我們可以解剖一下createThunkMiddleware,在第一次調(diào)用createThunkMiddleware是在chain階段,即上面源碼分析的:
所以這里的next也就是第二次調(diào)用時的store.dispatch, 為了實現(xiàn)同一函數(shù)內(nèi)能執(zhí)行多次dispatch,我們會判斷如果action為函數(shù),則執(zhí)行action本身并把必要參數(shù)傳遞給它,否則則直接觸發(fā)dispatch,這樣我們就實現(xiàn)了支持action為函數(shù)并且支持異步多dispatch的功能了,讀到這還是非常感嘆其設(shè)計的優(yōu)雅和簡潔,不經(jīng)讓筆者感嘆:學好函數(shù)式,走遍天下都不怕!
最后筆者準備了一個基于React+redux+redux-thunk的實戰(zhàn)項目,github地址:
https://github.com/MrXujiang/redux_OA
感興趣的可以學習參考一下。
最后
如果想學習更多H5游戲, webpack,node,gulp,css3,javascript,nodeJS,canvas數(shù)據(jù)可視化等前端知識和實戰(zhàn),歡迎在公號《趣談前端》加入我們的技術(shù)群一起學習討論,共同探索前端的邊界。
本文轉(zhuǎn)載自微信公眾號「趣談前端」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系趣談前端公眾號。