學習 redux 源碼整體架構,深入理解 redux 及其中間件原理

如果覺得內容不錯,可以設為星標置頂我的公眾號

1. 前言

你好,我是若川。這是學習源碼整體架構系列第八篇。整體架構這詞語好像有點大,姑且就算是源碼整體結構吧,主要就是學習是代碼整體結構,不深究其他不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。

要是有人說到怎么讀源碼,正在讀文章的你能推薦我的源碼系列文章,那真是太好了學習源碼整體架構系列文章如下:

1.學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫
2.學習 underscore 源碼整體架構,打造屬于自己的函數式編程類庫
3.學習 lodash 源碼整體架構,打造屬于自己的函數式編程類庫
4.學習 sentry 源碼整體架構,打造屬于自己的前端異常監控SDK
5.學習 vuex 源碼整體架構,打造屬于自己的狀態管理庫
6.學習 axios 源碼整體架構,打造屬于自己的請求庫
7.學習 koa 源碼的整體架構,淺析koa洋蔥模型原理和co原理

感興趣的讀者可以點擊閱讀。
其他源碼計劃中的有:expressvue-rotuerredux、 ?react-redux 等源碼,不知何時能寫完(哭泣),歡迎持續關注我(若川)。

源碼類文章,一般閱讀量不高。已經有能力看懂的,自己就看了。不想看,不敢看的就不會去看源碼。
所以我的文章,盡量寫得讓想看源碼又不知道怎么看的讀者能看懂。

閱讀本文你將學到:

  1. git subtree 管理子倉庫

  2. 如何學習 redux 源碼

  3. redux 中間件原理

  4. redux 各個API的實現

  5. vuexredux ?的對比

  6. 等等

1.1 本文閱讀最佳方式

把我的redux源碼倉庫 git clone https://github.com/lxchuan12/redux-analysis.git克隆下來,順便star一下我的redux源碼學習倉庫^_^。跟著文章節奏調試和示例代碼調試,用chrome動手調試印象更加深刻。文章長段代碼不用細看,可以調試時再細看。看這類源碼文章百遍,可能不如自己多調試幾遍。也歡迎加我微信交流ruochuan12

2. git subtree 管理子倉庫

寫了很多源碼文章,vuexaxioskoa等都是使用新的倉庫克隆一份源碼在自己倉庫中。雖然電腦可以拉取最新代碼,看到原作者的git信息。但上傳到github后。讀者卻看不到原倉庫作者的git信息了。于是我找到了git submodules 方案,但并不是很適合。再后來發現了git subtree

簡單說下 npm packagegit subtree的區別。npm package是單向的。git subtree則是雙向的。

具體可以查看這篇文章@德來(原有贊大佬):用 Git Subtree 在多個 Git 項目間雙向同步子項目,附簡明使用手冊

學會了git subtree后,我新建了redux-analysis項目后,把redux源碼4.x(截止至2020年06月13日,4.x分支最新版本是4.0.5master分支是ts,文章中暫不想讓一些不熟悉ts的讀者看不懂)分支克隆到了我的項目里的一個子項目,得以保留git信息。

對應命令則是:

git?subtree?add?--prefix=redux?https://github.com/reduxjs/redux.git?4.x

3. 調試 redux 源碼準備工作

之前,我在知乎回答了一個問題若川:一年內的前端看不懂前端框架源碼怎么辦?推薦了一些資料,閱讀量還不錯,大家有興趣可以看看。主要有四點:

1.借助調試
2.搜索查閱相關高贊文章
3.把不懂的地方記錄下來,查閱相關文檔
4.總結

看源碼調試很重要,所以我的每篇源碼文章都詳細描述(也許有人看來是比較啰嗦...)如何調試源碼。

斷點調試要領:
賦值語句可以一步按F10跳過,看返回值即可,后續詳細再看。
函數執行需要斷點按F11跟著看,也可以結合注釋和上下文倒推這個函數做了什么。
有些不需要細看的,直接按F8走向下一個斷點
刷新重新調試按F5

調試源碼前,先簡單看看 redux 的工作流程,有個大概印象。

redux 工作流程

3.1 rollup 生成 sourcemap 便于調試

修改rollup.config.js文件,output輸出的配置生成sourcemap

//?redux/rollup.config.js?有些省略
const?sourcemap?=?{sourcemap:?true,
};output:?{//?......sourcemap,
}

安裝依賴

git?clone?http://github.com/lxchuan12/redux-analysis.git
cd?redux-analysi/redux
npm?i
npm?run?build
#?編譯結束后會生成 sourcemap .map格式的文件到 dist、es、lib 目錄下。

仔細看看redux/examples目錄和redux/README

這時我在根路徑下,新建文件夾examples,把原生js寫的計數器redux/examples/counter-vanilla/index.html,復制到examples/index.html。同時把打包后的包含sourcemapredux/dist目錄,復制到examples/dist目錄。

修改index.htmlscriptredux.js文件為dist中的路徑

為了便于區分和調試后續html文件,我把index.html重命名為index.1.redux.getState.dispatch.html

#?redux-analysis?根目錄
#?安裝啟動服務的npm包
npm?i?-g?http-server
cd?examples
hs?-p?5000

就可以開心的調試啦。可以直接克隆我的項目git clone http://github.com/lxchuan12/redux-analysis.git。本地調試,動手實踐,容易消化吸收。

4. 通過調試計數器例子的學習 redux 源碼

接著我們來看examples/index.1.redux.getState.dispatch.html文件。先看html部分。只是寫了幾個 button,比較簡單。

<div><p>Clicked:?<span?id="value">0</span>?times<button?id="increment">+</button><button?id="decrement">-</button><button?id="incrementIfOdd">Increment?if?odd</button><button?id="incrementAsync">Increment?async</button></p>
</div>

js部分,也比較簡單。聲明了一個counter函數,傳遞給Redux.createStore(counter),得到結果store,而store是個對象。render方法渲染數字到頁面。用store.subscribe(render)訂閱的render方法。還有store.dispatch({type: 'INCREMENT' })方法,調用store.dispatch時會觸發render方法。這樣就實現了一個計數器。

function?counter(state,?action)?{if?(typeof?state?===?'undefined')?{return?0}switch?(action.type)?{case?'INCREMENT':return?state?+?1case?'DECREMENT':return?state?-?1default:return?state}
}var?store?=?Redux.createStore(counter)
var?valueEl?=?document.getElementById('value')function?render()?{valueEl.innerHTML?=?store.getState().toString()
}
render()
store.subscribe(render)document.getElementById('increment')
.addEventListener('click',?function?()?{store.dispatch({?type:?'INCREMENT'?})
})//?省略部分暫時無效代碼...

思考:看了這段代碼,你會在哪打斷點來調試呢。

//?四處可以斷點來看
//?1.
var?store?=?Redux.createStore(counter)
//?2.
function?render()?{
valueEl.innerHTML?=?store.getState().toString()
}
render()
//?3.
store.subscribe(render)
//?4.
store.dispatch({?type:?'INCREMENT'?})
redux debugger圖

圖中的右邊Scope,有時需要關注下,會顯示閉包、全局環境、當前環境等變量,還可以顯示函數等具體代碼位置,能幫助自己理解代碼。

斷點調試,按F5刷新頁面后,按F8,把鼠標放在Reduxstore上。

可以看到Redux上有好幾個方法。分別是:

  • __DO_NOT_USE__ActionTypes: {INIT: "@@redux/INITu.v.d.u.6.r", REPLACE: "@@redux/REPLACEg.u.u.7.c", PROBE_UNKNOWN_ACTION: ?}

  • applyMiddleware: ? applyMiddleware() 函數是一個增強器,組合多個中間件,最終增強store.dispatch函數,dispatch時,可以串聯執行所有中間件。

  • bindActionCreators: ? bindActionCreators(actionCreators, dispatch) 生成actions,主要用于其他庫,比如react-redux

  • combineReducers: ? combineReducers(reducers) 組合多個reducers,返回一個總的reducer函數。

  • compose: ? compose() 組合多個函數,從右到左,比如:compose(f, g, h) 最終得到這個結果 (...args) => f(g(h(...args))).

  • createStore: ? createStore(reducer, preloadedState, enhancer) 生成 store 對象

再看store也有幾個方法。分別是:

  • dispatch: ? dispatch(action) ?派發動作,也就是把subscribe收集的函數,依次遍歷執行

  • subscribe: ? subscribe(listener) 訂閱收集函數存在數組中,等待觸發dispatch依次執行。返回一個取消訂閱的函數,可以取消訂閱監聽。

  • getState: ? getState() 獲取存在createStore函數內部閉包的對象。

  • replaceReducer: ? replaceReducer(nextReducer) 主要用于redux開發者工具,對比當前和上一次操作的異同。有點類似時間穿梭功能。

  • Symbol(observable): ? observable()

也就是官方文檔redux.org.js上的 API

暫時不去深究每一個API的實現。重新按F5刷新頁面,斷點到var store = Redux.createStore(counter)。一直按F11,先走一遍主流程。

4.1 Redux.createSotre

createStore 函數結構是這樣的,是不是看起來很簡單,最終返回對象store,包含dispatchsubscribegetStatereplaceReducer等方法。

//?省略了若干代碼
export?default?function?createStore(reducer,?preloadedState,?enhancer)?{//?省略參數校驗和替換//?當前的?reducer?函數let?currentReducer?=?reducer//?當前statelet?currentState?=?preloadedState//?當前的監聽數組函數let?currentListeners?=?[]//?下一個監聽數組函數let?nextListeners?=?currentListeners//?是否正在dispatch中let?isDispatching?=?falsefunction?ensureCanMutateNextListeners()?{if?(nextListeners?===?currentListeners)?{nextListeners?=?currentListeners.slice()}}function?getState()?{return?currentState}function?subscribe(listener)?{}function?dispatch(action)?{}function?replaceReducer(nextReducer)?{}function?observable()?{}//?ActionTypes.INIT?@@redux/INITu.v.d.u.6.rdispatch({?type:?ActionTypes.INIT?})return?{dispatch,subscribe,getState,replaceReducer,[$$observable]:?observable}
}

4.2 store.dispatch(action)

function?dispatch(action)?{//?判斷action是否是對象,不是則報錯if?(!isPlainObject(action))?{throw?new?Error('Actions?must?be?plain?objects.?'?+'Use?custom?middleware?for?async?actions.')}//?判斷action.type?是否存在,沒有則報錯if?(typeof?action.type?===?'undefined')?{throw?new?Error('Actions?may?not?have?an?undefined?"type"?property.?'?+'Have?you?misspelled?a?constant?')}//?不是則報錯if?(isDispatching)?{throw?new?Error('Reducers?may?not?dispatch?actions.')}try?{isDispatching?=?truecurrentState?=?currentReducer(currentState,?action)}?finally?{//?調用完后置為?falseisDispatching?=?false}//??把?收集的函數拿出來依次調用const?listeners?=?(currentListeners?=?nextListeners)for?(let?i?=?0;?i?<?listeners.length;?i++)?{const?listener?=?listeners[i]listener()}//?最終返回?actionreturn?action}
var?store?=?Redux.createStore(counter)

上文調試完了這句。

繼續按F11調試。

function?render()?{valueEl.innerHTML?=?store.getState().toString()
}
render()

4.3 store.getState()

getState函數實現比較簡單。

function?getState()?{//?判斷正在dispatch中,則報錯if?(isDispatching)?{throw?new?Error('You?may?not?call?store.getState()?while?the?reducer?is?executing.?'?+'The?reducer?has?already?received?the?state?as?an?argument.?'?+'Pass?it?down?from?the?top?reducer?instead?of?reading?it?from?the?store.')}//?返回當前的statereturn?currentState
}

4.4 store.subscribe(listener)

訂閱監聽函數,存放在數組中,store.dispatch(action)時遍歷執行。

function?subscribe(listener)?{//?訂閱參數校驗不是函數報錯if?(typeof?listener?!==?'function')?{throw?new?Error('Expected?the?listener?to?be?a?function.')}//?正在dispatch中,報錯if?(isDispatching)?{throw?new?Error('You?may?not?call?store.subscribe()?while?the?reducer?is?executing.?'?+'If?you?would?like?to?be?notified?after?the?store?has?been?updated,?subscribe?from?a?'?+'component?and?invoke?store.getState()?in?the?callback?to?access?the?latest?state.?'?+'See?https://redux.js.org/api-reference/store#subscribelistener?for?more?details.')}//?訂閱為?truelet?isSubscribed?=?trueensureCanMutateNextListeners()nextListeners.push(listener)//?返回一個取消訂閱的函數return?function?unsubscribe()?{if?(!isSubscribed)?{return}//?正在dispatch中,則報錯if?(isDispatching)?{throw?new?Error('You?may?not?unsubscribe?from?a?store?listener?while?the?reducer?is?executing.?'?+'See?https://redux.js.org/api-reference/store#subscribelistener?for?more?details.')}//?訂閱為?falseisSubscribed?=?falseensureCanMutateNextListeners()//???找到當前監聽函數const?index?=?nextListeners.indexOf(listener)//???在數組中刪除nextListeners.splice(index,?1)currentListeners?=?null}}

到這里,我們就調試學習完了Redux.createSotrestore.dispatchstore.getStatestore.subscribe的源碼。

接下來,我們寫個中間件例子,來調試中間件相關源碼。

5. Redux 中間件相關源碼

中間件是重點,面試官也經常問這類問題。

5.1 Redux.applyMiddleware(...middlewares)

5.1.1 準備 logger 例子調試

為了調試Redux.applyMiddleware(...middlewares),我在examples/js/middlewares.logger.example.js寫一個簡單的logger例子。分別有三個logger1logger2logger3函數。由于都是類似,所以我在這里只展示logger1函數。

//?examples/js/middlewares.logger.example.js
function?logger1({?getState?})?{return?next?=>?action?=>?{console.log('will?dispatch--1--next,?action:',?next,?action)//?Call?the?next?dispatch?method?in?the?middleware?chain.const?returnValue?=?next(action)console.log('state?after?dispatch--1',?getState())//?This?will?likely?be?the?action?itself,?unless//?a?middleware?further?in?chain?changed?it.return?returnValue}
}
//?省略?logger2、logger3

logger中間件函數做的事情也比較簡單,返回兩層函數,next就是下一個中間件函數,調用返回結果。為了讓讀者能看懂,我把logger1用箭頭函數、logger2則用普通函數。

寫好例子后,我們接著來看怎么調試Redux.applyMiddleware(...middlewares))源碼。

cd?redux-analysis?&&?hs?-p?5000
#?上文說過npm?i?-g?http-server

打開http://localhost:5000/examples/index.2.redux.applyMiddleware.compose.html,按F12打開控制臺,

先點擊加號操作+1,把結果展示出來。

redux 中間件調試圖

從圖中可以看出,next則是下一個函數。先1-2-3,再3-2-1這樣的順序。

這種也就是我們常說的中間件,面向切面編程(AOP)。

中間件圖解

接下來調試,在以下語句打上斷點和一些你覺得重要的地方打上斷點。

//?examples/index.2.redux.applyMiddleware.compose.html
var?store?=?Redux.createStore(counter,?Redux.applyMiddleware(logger1,?logger2,??logger3))

5.1.2 Redux.applyMiddleware(...middlewares) 源碼

//?redux/src/applyMiddleware.js
/***?...*?@param?{...Function}?middlewares?The?middleware?chain?to?be?applied.*?@returns?{Function}?A?store?enhancer?applying?the?middleware.*/
export?default?function?applyMiddleware(...middlewares)?{return?createStore?=>?(...args)?=>?{const?store?=?createStore(...args)let?dispatch?=?()?=>?{throw?new?Error('Dispatching?while?constructing?your?middleware?is?not?allowed.?'?+'Other?middleware?would?not?be?applied?to?this?dispatch.')}const?middlewareAPI?=?{getState:?store.getState,dispatch:?(...args)?=>?dispatch(...args)}const?chain?=?middlewares.map(middleware?=>?middleware(middlewareAPI))dispatch?=?compose(...chain)(store.dispatch)return?{...store,dispatch}}
}
//?redux/src/createStore.js
export?default?function?createStore(reducer,?preloadedState,?enhancer)?{//?省略參數校驗//?如果第二個參數`preloadedState`是函數,并且第三個參數`enhancer`是undefined,把它們互換一下。if?(typeof?preloadedState?===?'function'?&&?typeof?enhancer?===?'undefined')?{enhancer?=?preloadedStatepreloadedState?=?undefined}if?(typeof?enhancer?!==?'undefined')?{if?(typeof?enhancer?!==?'function')?{throw?new?Error('Expected?the?enhancer?to?be?a?function.')}//?enhancer?也就是`Redux.applyMiddleware`返回的函數//?createStore?的?args?則是?`reducer,?preloadedState`/***?createStore?=>?(...args)?=>?{const?store?=?createStore(...args)return?{...store,dispatch,}}**?///?最終返回增強的store對象。return?enhancer(createStore)(reducer,?preloadedState)}//?省略后續代碼
}

把接收的中間件函數logger1, logger2, logger3放入到 了middlewares數組中。Redux.applyMiddleware最后返回兩層函數。把中間件函數都混入了參數getStatedispatch

//?examples/index.2.redux.applyMiddleware.compose.html
var?store?=?Redux.createStore(counter,?Redux.applyMiddleware(logger1,?logger2,??logger3))

最后這句其實是返回一個增強了dispatchstore對象。

而增強的dispatch函數,則是用Redux.compose(...functions)進行串聯起來執行的。

5.2 Redux.compose(...functions)

export?default?function?compose(...funcs)?{if?(funcs.length?===?0)?{return?arg?=>?arg}if?(funcs.length?===?1)?{return?funcs[0]}return?funcs.reduce((a,?b)?=>?(...args)?=>?a(b(...args)))
}
//?applyMiddleware.js
dispatch?=?compose(...chain)(store.dispatch)
//?compose
funcs.reduce((a,?b)?=>?(...args)?=>?a(b(...args)))

這兩句可能不是那么好理解,可以斷點多調試幾次。我把箭頭函數轉換成普通函數。

funcs.reduce(function(a,?b){return?function(...args){return?a(b(...args));};
});

其實redux源碼中注釋很清晰了,這個compose函數上方有一堆注釋,其中有一句:組合多個函數,從右到左,比如:compose(f, g, h) 最終得到這個結果 (...args) => f(g(h(...args))).

5.2.1 compose 函數演化

Redux.compose(...functions)函數源碼后,還是不明白,不要急不要慌,吃完雞蛋還有湯。仔細來看如何演化而來,先來簡單看下如下需求。

傳入一個數值,計算數值乘以10再加上10,再減去2。

實現起來很簡單。

const?calc?=?(num)?=>?num?*?10?+?10?-?2;
calc(10);?//?108

但這樣寫有個問題,不好擴展,比如我想乘以10時就打印出結果。為了便于擴展,我們分開寫成三個函數。

const?multiply?=?(x)?=>?{const?result?=?x?*?10;console.log(result);return?result;
};
const?add?=?(y)?=>?y?+?10;
const?minus?=?(z)?=>?z?-?2;//?計算結果
console.log(minus(add(multiply(10))));
//?100
//?108
//?這樣我們就把三個函數計算結果出來了。

再來實現一個相對通用的函數,計算這三個函數的結果。

const?compose?=?(f,?g,?h)?=>?{return?function(x){return?f(g(h(x)));}
}
const?calc?=?compose(minus,?add,?multiply);
console.log(calc(10));
//?100
//?108

這樣還是有問題,只支持三個函數。我想支持多個函數。我們了解到數組的reduce方法就能實現這樣的功能。前一個函數

//?我們常用reduce來計算數值數組的總和
[1,2,3,4,5].reduce((pre,?item,?index,?arr)?=>?{console.log('(pre,?item,?index,?arr)',?pre,?item,?index,?arr);//?(pre,?item,?index,?arr)?1?2?1?(5)?[1,?2,?3,?4,?5]//?(pre,?item,?index,?arr)?3?3?2?(5)?[1,?2,?3,?4,?5]//?(pre,?item,?index,?arr)?6?4?3?(5)?[1,?2,?3,?4,?5]//?(pre,?item,?index,?arr)?10?5?4?(5)?[1,?2,?3,?4,?5]return?pre?+?item;
});
//?15

pre 是上一次返回值,在這里是數值1,3,6,10。在下一個例子中則是匿名函數。

function(x){return?a(b(x));
}

item2,3,4,5,在下一個例子中是minus、add、multiply

const?compose?=?(...funcs)?=>?{return?funcs.reduce((a,?b)?=>?{return?function(x){return?a(b(x));}})
}
const?calc?=?compose(minus,?add,?multiply);
console.log(calc(10));
//?100
//?108

Redux.compose(...functions)其實就是這樣,只不過中間件是返回雙層函數罷了。

所以返回的是next函數,他們串起來執行了,形成了中間件的洋蔥模型。人們都說一圖勝千言。我畫了一個相對簡單的redux中間件原理圖。

redux中間件原理圖

如果還不是很明白,建議按照我給出的例子,多調試。

cd?redux-analysis?&&?hs?-p?5000
#?上文說過npm?i?-g?http-server

打開http://localhost:5000/examples/index.3.html,按F12打開控制臺調試。

5.2.2 前端框架的 compose 函數的實現

lodash源碼中 compose函數的實現,也是類似于數組的reduce,只不過是內部實現的arrayReduce

引用自我的文章:學習lodash源碼整體架構

//?lodash源碼
function?baseWrapperValue(value,?actions)?{var?result?=?value;//?如果是lazyWrapper的實例,則調用LazyWrapper.prototype.value?方法,也就是?lazyValue?方法if?(result?instanceof?LazyWrapper)?{result?=?result.value();}//?類似?[].reduce(),把上一個函數返回結果作為參數傳遞給下一個函數return?arrayReduce(actions,?function(result,?action)?{return?action.func.apply(action.thisArg,?arrayPush([result],?action.args));},?result);
}

koa-compose源碼也有compose函數的實現。實現是循環加promise。由于代碼比較長我就省略了,具體看鏈接若川:學習 koa 源碼的整體架構,淺析koa洋蔥模型原理和co原理小節 koa-compose 源碼(洋蔥模型實現)

6. Redux.combineReducers(reducers)

打開http://localhost:5000/examples/index.4.html,按F12打開控制臺,按照給出的例子,調試接下來的Redux.combineReducers(reducers)Redux.bindActionCreators(actionCreators, dispatch)具體實現。由于文章已經很長了,這兩個函數就不那么詳細解釋了。

combineReducers函數簡單來說就是合并多個reducer為一個函數combination

export?default?function?combineReducers(reducers)?{const?reducerKeys?=?Object.keys(reducers)const?finalReducers?=?{}for?(let?i?=?0;?i?<?reducerKeys.length;?i++)?{const?key?=?reducerKeys[i]//?省略一些開發環境判斷的代碼...if?(typeof?reducers[key]?===?'function')?{finalReducers[key]?=?reducers[key]}}//?經過一些處理后得到最后的finalReducerKeysconst?finalReducerKeys?=?Object.keys(finalReducers)//?省略一些開發環境判斷的代碼...return?function?combination(state?=?{},?action)?{//?...?省略開發環境的一些判斷//?用?hasChanged變量?記錄前后?state?是否已經修改let?hasChanged?=?false//?聲明對象來存儲下一次的stateconst?nextState?=?{}//遍歷?finalReducerKeysfor?(let?i?=?0;?i?<?finalReducerKeys.length;?i++)?{const?key?=?finalReducerKeys[i]const?reducer?=?finalReducers[key]const?previousStateForKey?=?state[key]//?執行?reducerconst?nextStateForKey?=?reducer(previousStateForKey,?action)//?省略容錯代碼?...nextState[key]?=?nextStateForKey//?兩次?key?對比?不相等則發生改變hasChanged?=?hasChanged?||?nextStateForKey?!==?previousStateForKey}//?最后的?keys?數組對比?不相等則發生改變hasChanged?=hasChanged?||?finalReducerKeys.length?!==?Object.keys(state).lengthreturn?hasChanged???nextState?:?state}
}

7. Redux.bindActionCreators(actionCreators, dispatch)

如果第一個參數是一個函數,那就直接返回一個函數。如果是一個對象,則遍歷賦值,最終生成boundActionCreators對象。

function?bindActionCreator(actionCreator,?dispatch)?{return?function()?{return?dispatch(actionCreator.apply(this,?arguments))}
}export?default?function?bindActionCreators(actionCreators,?dispatch)?{if?(typeof?actionCreators?===?'function')?{return?bindActionCreator(actionCreators,?dispatch)}//?...?省略一些容錯判斷const?boundActionCreators?=?{}for?(const?key?in?actionCreators)?{const?actionCreator?=?actionCreators[key]if?(typeof?actionCreator?===?'function')?{boundActionCreators[key]?=?bindActionCreator(actionCreator,?dispatch)}}return?boundActionCreators
}

redux所提供的的API 除了store.replaceReducer(nextReducer)沒分析,其他都分析了。

8. vuex 和 redux 簡單對比

8.1 源碼實現形式

從源碼實現上來看,vuex源碼主要使用了構造函數,而redux則是多用函數式編程、閉包。

8.2 耦合度

vuexvue 強耦合,脫離了vue則無法使用。而reduxreact沒有關系,所以它可以使用于小程序或者jQuery等。如果需要和react使用,還需要結合react-redux庫。

8.3 擴展

//?logger?插件,具體實現省略
function?logger?(store)?{console.log('store',?store);
}
//?作為數組傳入
new?Vuex.Store({state,getters,actions,mutations,plugins:?process.env.NODE_ENV?!==?'production'??[logger]:?[]
})
//?vuex?源碼?插件執行部分
class?Store{constructor(){//?把vuex的實例對象?store整個對象傳遞給插件使用plugins.forEach(plugin?=>?plugin(this))}
}

vuex實現擴展則是使用插件形式,而redux是中間件的形式。redux的中間件則是AOP(面向切面編程),reduxRedux.applyMiddleware()其實也是一個增強函數,所以也可以用戶來實現增強器,所以redux生態比較繁榮。

8.4 上手難易度

相對來說,vuex上手相對簡單,redux相對難一些,redux涉及到一些函數式編程、高階函數、純函數等概念。

9. 總結

文章主要通過一步步調試的方式循序漸進地講述redux源碼的具體實現。旨在教會讀者調試源碼,不懼怕源碼。

面試官經常喜歡考寫一個redux中間件,說說redux中間件的原理。

function?logger1({?getState?})?{return?next?=>?action?=>?{const?returnValue?=?next(action)return?returnValue}
}
const?compose?=?(...funcs)?=>?{if?(funcs.length?===?0)?{return?arg?=>?arg}if?(funcs.length?===?1)?{return?funcs[0]}//?箭頭函數//?return?funcs.reduce((a,?b)?=>?(...args)?=>?a(b(...args)))return?funcs.reduce((a,?b)?=>?{return?function(x){return?a(b(x));}})
}
const?enhancerStore?=?Redux.create(reducer,?Redux.applyMiddleware(logger1,?...))
enhancerStore.dispatch(action)

用戶觸發enhancerStore.dispatch(action)是增強后的,其實就是第一個中間件函數,中間的next是下一個中間件函數,最后next是沒有增強的store.dispatch(action)

最后再來看張redux工作流程圖

是不是就更理解些了呢。

如果讀者發現有不妥或可改善之處,再或者哪里沒寫明白的地方,歡迎評論指出。另外覺得寫得不錯,對你有些許幫助,可以點贊、評論、轉發分享,也是對我的一種支持,非常感謝呀。要是有人說到怎么讀源碼,正在讀文章的你能推薦我的源碼系列文章,那真是太好了

一般人都看不到文章末尾,看到這里你已經超越90%的人了。

  1. 覺得文章不錯,可以點個在看呀^_^另外歡迎留言交流~

  2. 加我(若川)微信ruochuan12,拉你進交流群,長期交流學習

  3. 關注我的公眾號若川視野,回復pdf領取前端優質書籍pdf

  4. 我的博客地址:https://lxchuan12.cn 歡迎收藏

小提醒:若川視野公眾號原創文章合集在菜單欄中間【原創精選】按鈕,歡迎點擊閱讀。

由于公眾號限制外鏈,點擊閱讀原文,或許閱讀體驗更佳

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276456.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276456.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276456.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

pdf安裝包_有么有pdf控件,不需要用戶安裝任何安裝包直接打印的?

如果開發一個軟件&#xff0c;需要用到PDF功能&#xff0c;您的選擇是基于Adobe PDF嗎&#xff1f; 如果是基于Adobe PDF&#xff0c;需要用戶安裝一個幾十M的Adobe的安裝包&#xff0c;這顯然是不友好的。即使目前也有了一些其它的閱讀器&#xff0c;大小也還好。但是&#xf…

Centos編譯安裝Apache 2.4.6筆記 配置

轉載鏈接&#xff1a;http://www.onepx.com/centos-apache-246.html 之前服務器 Apache 版本一直是 2.2.x&#xff0c;鑒于 Centos 更新軟件的惰性&#xff0c;我看直到 2014 年結束&#xff0c;apache 2.4 都不一定會出現在 Centos 中&#xff0c;我是不打算等了&#xff0c;…

[轉] C#異步操作

Title 通過委托實現異步調用中BeginInvoke及回調函數的使用 通過委托實現異步調用的步驟&#xff1a; 1.定義委托。 2.將要進行異步調用的方法“實例化”到定義的委托。 3.在委托上調用BeginInvoke方法。其中&#xff0c;BeginInvoke的參數由三個部分構成。第一部分&#xff1…

HTTP Server Error 500 內部服務器錯誤

問題&#xff1a;HTTP500錯誤 或 Server Application Error ------------------------------------Server Application ErrorThe server has encountered an error while loading an application during the processing of your request. Please refer to the event log for mo…

使用 ohmyzsh 打造 windows、ubuntu、mac 系統高效終端命令行工具

如果覺得內容不錯&#xff0c;可以設為星標置頂我的公眾號原標題名&#xff1a;oh my zsh 和 windows git bash 設置別名提高效率寫于2018年06月03日在我的微信交流群中聽聞很多前端開發比較貧窮&#xff0c;沒有買mac電腦&#xff08;比如我&#xff09;&#xff0c;也沒有用過…

request獲取mac地址_【Go】獲取用戶真實的ip地址

原文鏈接&#xff1a;https://blog.thinkeridea.com/201903/go/get_client_ip.html用戶請求到達提供服務的服務器中間有很多的環節&#xff0c;導致服務獲取用戶真實的 ip 非常困難&#xff0c;大多數的框架及工具庫都會封裝各種獲取用戶真實 ip 的方法&#xff0c;在 exnet 包…

Installation of Apache HTTPD

轉載鏈接&#xff1a;http://www.linuxfromscratch.org/blfs/view/svn/server/apache.html Installation of Apache-2.4.7 HTTPD For security reasons, running the server as an unprivileged user and group is strongly encouraged. Create the following group and user…

iPhone開發四劍客之《Objective-C基礎教程》

iPhone 開發四劍客之《Objective-C 基礎教程》 Objective-C 語言是 C 語言的一個擴展集&#xff0c;許多&#xff08;可能是大多數&#xff09;具備 Mac OS X 外觀的應用程序都是使用該語言開發的。它以 C 語言為基礎&#xff0c;添加了一些微妙但意義重大的特性。 蘋果公司為…

教師節,你記憶中老師說過印象最深的是什么話?(抽獎)

我記憶中老師說過印象最深的話小學老師&#xff1a;1、小學語文老師李老師說&#xff0c;以后你們可能帶個手機就可以支付了~不需要帶現金。&#xff08;在杭州確實實現了&#xff0c;用支付寶即可&#xff09; 2、小學數學老師李老師說&#xff1a;好好讀書的目的是啥&#xf…

Spark List組件滾動條加事件使datalist數據發生變化

<?xml version"1.0" encoding"utf-8"?><!-- http://blog.flexexamples.com/2009/05/31/detecting-when-the-vertical-scroll-bar-is-scrolled-on-a-spark-list-control-in-flex-4/ --><s:Application name"Spark_List_scroller_vert…

keras訓練完以后怎么預測_還在使用“龜速”的單顯卡訓練模型?動動手,讓TPU節省你的時間...

點擊上方關注&#xff0c;All in AI中國本文將介紹如何使用Keras和Google CoLaboratory與TPU一起訓練LSTM模型&#xff0c;與本地計算機上的GPU相比&#xff0c;這樣訓練能大大縮短訓練時間。很長一段時間以來&#xff0c;我都在單張GTX 1070顯卡上訓練我的模型&#xff0c;它的…

PHP5加載|安裝外部C動態庫

[1] cd php-5.3.9/ext[2] ./ext_skel --extnamencdocxml[3] cd ncdocxml[4] nano -w config.m4############刪除 3 個 dnldnl PHP_ARG_WITH(my_module, for my_module support,dnl Make sure that the comment is aligned:dnl [ --with-my_module Include my_module support])或…

手把手教你寫個小程序定時器管理庫

背景凹凸曼是個小程序開發者&#xff0c;他要在小程序實現秒殺倒計時。于是他不假思索&#xff0c;寫了以下代碼&#xff1a;Page({init: function () {clearInterval(this.timer)this.timer setInterval(() > {// 倒計時計算邏輯console.log(setInterval)})}, })可是&…

[New Portal]Windows Azure Virtual Machine (14) 在本地制作數據文件VHD并上傳至Azure(1)

《Windows Azure Platform 系列文章目錄》 之前的內容里&#xff0c;我介紹了如何將本地的Server 2012中文版 VHD上傳至Windows Azure&#xff0c;并創建基于該Server 2012 VHD的虛擬機。 我們知道&#xff0c;VHD不僅僅可以保存操作系統&#xff0c;而且可以保存數據文件。 如…

python 退出程序_Python:用Ctrl+C解決終止多線程程序的問題!(建議收藏)

前言&#xff1a;今天為大家帶來的內容是Python:用CtrlC解決終止多線程程序的問題&#xff01;文章中的代碼具有不錯的參考意義&#xff0c;希望在此能夠幫助到各位&#xff01;(多數代碼用圖片的方式呈現出來&#xff0c;方便各位觀看與收藏)出發點&#xff1a;前段時間&#…

Mysql InnoDB Plugin安裝 install

轉載鏈接&#xff1a;http://www.orczhou.com/index.php/2010/03/innodb-plugin-setup/ InnoDB Plugin較之Built-in版本新增了很多特性&#xff1a;包括快速DDL、壓縮存儲等&#xff0c;而且引入了全新的文件格式Barracuda。眾多測試也表明&#xff0c;Plugin在很多方面優于Bu…

Hibernate的數據過濾查詢

數據過濾并不是一種常規的數據查詢方法&#xff0c;而是一種整體的篩選方法。數據過濾也可對數據進行篩選&#xff0c;因此&#xff0c;將其放在Hibernate的數據查詢框架中介紹。 如果一旦啟用了數據過濾器&#xff0c;則不管數據查詢&#xff0c;還是數據加載&#xff0c;該過…

若川知乎高贊:有哪些必看的 JS 庫?

歡迎星標我的公眾號&#xff0c;回復加群&#xff0c;長期交流學習我的知乎回答目前2w閱讀量&#xff0c;270贊&#xff0c;現在發到公眾號聲明原創。必看的js庫&#xff1f;只有當前階段值不值看。我從去年7月起看一些前端庫的源碼&#xff0c;歷時一年才寫了八篇《學習源碼整…

python用for循環求10的因數_python for循環練習(初級)

for循環練習1for i in range(4):print(i)D:\尚硅谷Python\venv\Scripts\python.exe D:/尚硅谷Python/test.py0123for循環練習2for x in range(1,40,5): #間隔5print(x)D:\尚硅谷Python\venv\Scripts\python.exe D:/尚硅谷Python/test.py16111621263136打印99乘法表for i in ran…

基于EasyUI的Web應用程序及過去一年的總結

前言 一個多月之前已經提交了離職申請&#xff0c;好在領導都已經批準了&#xff0c;過幾天就辦理手續了&#xff0c;在此感謝領導的栽培與挽留&#xff0c;感謝各位同事在工作中的給我的幫助&#xff0c;離開這個團隊確實有一些不舍&#xff0c;不為別的&#xff0c;只因為這個…