学习 redux 源码整体架构,深入理解 redux 及其中间件原理
如果覺得內容不錯,可以設為星標置頂我的公眾號
1. 前言
你好,我是若川。這是學習源碼整體架構系列第八篇。整體架構這詞語好像有點大,姑且就算是源碼整體結構吧,主要就是學習是代碼整體結構,不深究其他不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。
要是有人說到怎么讀源碼,正在讀文章的你能推薦我的源碼系列文章,那真是太好了。學習源碼整體架構系列文章如下:
1.學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫
2.學習 underscore 源碼整體架構,打造屬于自己的函數式編程類庫
3.學習 lodash 源碼整體架構,打造屬于自己的函數式編程類庫
4.學習 sentry 源碼整體架構,打造屬于自己的前端異常監控SDK
5.學習 vuex 源碼整體架構,打造屬于自己的狀態管理庫
6.學習 axios 源碼整體架構,打造屬于自己的請求庫
7.學習 koa 源碼的整體架構,淺析koa洋蔥模型原理和co原理
感興趣的讀者可以點擊閱讀。
其他源碼計劃中的有:express、vue-rotuer、redux、 ?react-redux 等源碼,不知何時能寫完(哭泣),歡迎持續關注我(若川)。
源碼類文章,一般閱讀量不高。已經有能力看懂的,自己就看了。不想看,不敢看的就不會去看源碼。
所以我的文章,盡量寫得讓想看源碼又不知道怎么看的讀者能看懂。
閱讀本文你將學到:
git subtree 管理子倉庫
如何學習 redux 源碼
redux 中間件原理
redux 各個API的實現
vuex 和 redux ?的對比
等等
1.1 本文閱讀最佳方式
把我的redux源碼倉庫 git clone https://github.com/lxchuan12/redux-analysis.git克隆下來,順便star一下我的redux源碼學習倉庫^_^。跟著文章節奏調試和示例代碼調試,用chrome動手調試印象更加深刻。文章長段代碼不用細看,可以調試時再細看。看這類源碼文章百遍,可能不如自己多調試幾遍。也歡迎加我微信交流ruochuan12。
2. git subtree 管理子倉庫
寫了很多源碼文章,vuex、axios、koa等都是使用新的倉庫克隆一份源碼在自己倉庫中。雖然電腦可以拉取最新代碼,看到原作者的git信息。但上傳到github后。讀者卻看不到原倉庫作者的git信息了。于是我找到了git submodules 方案,但并不是很適合。再后來發現了git subtree。
簡單說下 npm package和git subtree的區別。npm package是單向的。git subtree則是雙向的。
具體可以查看這篇文章@德來(原有贊大佬):用 Git Subtree 在多個 Git 項目間雙向同步子項目,附簡明使用手冊
學會了git subtree后,我新建了redux-analysis項目后,把redux源碼4.x(截止至2020年06月13日,4.x分支最新版本是4.0.5,master分支是ts,文章中暫不想讓一些不熟悉ts的讀者看不懂)分支克隆到了我的項目里的一個子項目,得以保留git信息。
對應命令則是:
git?subtree?add?--prefix=redux?https://github.com/reduxjs/redux.git?4.x3. 調試 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。同時把打包后的包含sourcemap的redux/dist目錄,復制到examples/dist目錄。
修改index.html的script的redux.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,把鼠標放在Redux和store上。
可以看到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,包含dispatch、subscribe、getState、replaceReducer等方法。
//?省略了若干代碼 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.createSotre、store.dispatch、store.getState、store.subscribe的源碼。
接下來,我們寫個中間件例子,來調試中間件相關源碼。
5. Redux 中間件相關源碼
中間件是重點,面試官也經常問這類問題。
5.1 Redux.applyMiddleware(...middlewares)
5.1.1 準備 logger 例子調試
為了調試Redux.applyMiddleware(...middlewares),我在examples/js/middlewares.logger.example.js寫一個簡單的logger例子。分別有三個logger1,logger2,logger3函數。由于都是類似,所以我在這里只展示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、logger3logger中間件函數做的事情也比較簡單,返回兩層函數,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最后返回兩層函數。把中間件函數都混入了參數getState和dispatch。
//?examples/index.2.redux.applyMiddleware.compose.html var?store?=?Redux.createStore(counter,?Redux.applyMiddleware(logger1,?logger2,??logger3))最后這句其實是返回一個增強了dispatch的store對象。
而增強的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; }); //?15pre 是上一次返回值,在這里是數值1,3,6,10。在下一個例子中則是匿名函數。
function(x){return?a(b(x)); }item是2,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 耦合度
vuex 與 vue 強耦合,脫離了vue則無法使用。而redux跟react沒有關系,所以它可以使用于小程序或者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(面向切面編程),redux中Redux.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%的人了。
覺得文章不錯,可以點個在看呀^_^另外歡迎留言交流~
加我(若川)微信ruochuan12,拉你進交流群,長期交流學習
關注我的公眾號若川視野,回復pdf領取前端優質書籍pdf
我的博客地址:https://lxchuan12.cn 歡迎收藏
小提醒:若川視野公眾號原創文章合集在菜單欄中間【原創精選】按鈕,歡迎點擊閱讀。
由于公眾號限制外鏈,點擊閱讀原文,或許閱讀體驗更佳
總結
以上是生活随笔為你收集整理的学习 redux 源码整体架构,深入理解 redux 及其中间件原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: L230 RF可靠性测试-RF指标
- 下一篇: 5分钟快速入门GIS(GIS基础)(GI