如果覺得內容不錯,可以設為星標置頂我的公眾號
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.x
3. 調試 redux 源碼準備工作
之前,我在知乎回答了一個問題若川:一年內的前端看不懂前端框架源碼怎么辦?推薦了一些資料,閱讀量還不錯,大家有興趣可以看看。主要有四點:
1.借助調試
2.搜索查閱相關高贊文章
3.把不懂的地方記錄下來,查閱相關文檔
4.總結
看源碼調試很重要,所以我的每篇源碼文章都詳細描述(也許有人看來是比較啰嗦...)如何調試源碼。
斷點調試要領:
賦值語句可以一步按F10
跳過,看返回值即可,后續詳細再看。
函數執行需要斷點按F11
跟著看,也可以結合注釋和上下文倒推這個函數做了什么。
有些不需要細看的,直接按F8
走向下一個斷點
刷新重新調試按F5
調試源碼前,先簡單看看 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'?})
圖中的右邊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、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,把結果展示出來。
從圖中可以看出,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;
});
//?15
pre
是上一次返回值,在這里是數值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 歡迎收藏
小提醒:若川視野公眾號原創文章合集在菜單欄中間
【原創精選】
按鈕,歡迎點擊閱讀。
由于公眾號限制外鏈,點擊
閱讀原文
,或許閱讀體驗更佳