Redux源碼解析系列 (二)牛鼻的createStore
前言
在上一章了解了【Redux 源碼解析系列(一) -- Redux的實(shí)現(xiàn)思想】之后,我們正式進(jìn)入源碼解析~
Redux 其實(shí)是用來幫我們管理狀態(tài)的一個(gè)框架,它暴露給我們四個(gè)接口,分別是:
- createStore
- combineReducers
- bindActionCreators
- applyMiddleware
- compose
本篇文章,我們來解析createStore
下面我來對其進(jìn)行解析~
INIT
這個(gè)方法是redux保留用的,用來初始化reducer的狀態(tài)
- export const ActionTypes = {
- INIT: '@@redux/INIT'
- }
前面說 createStore的作用就是:創(chuàng)建一個(gè)store來管理app的狀態(tài),唯一改變狀態(tài)的方式就是dispatch一個(gè)action,最終返回一個(gè)object。
- return {
- dispatch,
- subscribe,
- getState,
- replaceReducer,
- [$$observable]: observable
- }
不過replaceReducer,跟[$$observable]:都不常用~ ,所以這里只對前三的接口做解析。
createStore
在一個(gè)app里,只能有一個(gè)store,如果你想指明不同的state對應(yīng)不同的action,可以用combineReducers去合并不同的reducer。
參數(shù):
- reducer(function):就是通過傳入當(dāng)前State,還有action,計(jì)算出下一個(gè)state,返回回來。
- preloadedState(any):initial state
- enhancer(function):增強(qiáng)store的功能,讓它擁有第三方的功能,比如middleware.Redux里面唯一的enhancer就是applyMiddleware()
- export default function createStore(reducer, preloadedState, enhancer) {
- // 第一段說的就是當(dāng)?shù)诙€(gè)參數(shù)沒有傳preloadedState,而直接傳function的話,就會(huì)直接把這個(gè)function當(dāng)成enhancer
- if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
- enhancer = preloadedState
- preloadedState = undefined
- }
- // 當(dāng)?shù)谌齻€(gè)參數(shù)傳了但是不是function也會(huì)報(bào)錯(cuò)
- if (typeof enhancer !== 'undefined') {
- if (typeof enhancer !== 'function') {
- throw new Error('Expected the enhancer to be a function.')
- }
- //關(guān)鍵的一個(gè)就在于這里了,在前一篇講applyMiddleware的時(shí)候介紹了這么做的意義,
- //實(shí)際就是把createStore這件事在applyMiddleware里面做,轉(zhuǎn)移了鍋。
- return enhancer(createStore)(reducer, preloadedState)
- }
- if (typeof reducer !== 'function') {
- throw new Error('Expected the reducer to be a function.')
- }
- let currentReducer = reducer
- let currentState = preloadedState
- let currentListeners = []
- let nextListeners = currentListeners
- let isDispatching = false
- }
上面是第一個(gè)part,在校驗(yàn)完參數(shù)的正確之后,終于可以干點(diǎn)正事兒了。createStore最終會(huì)返回一個(gè)Object.
- {
- dispatch,
- subscribe,
- getState
- }
接下來看看里面都做了什么:
getState
getState作用就是將當(dāng)前state的狀態(tài)返回回來,沒啥好說的~
- function getState() {
- return currentState
- }
subscribe
作用:添加監(jiān)聽函數(shù)listener,它會(huì)在每次dispatch action的時(shí)候調(diào)用。
參數(shù):listener(function): 在每一次dispatch action的時(shí)候都會(huì)調(diào)用的函數(shù)
返回:返回一個(gè)移除listener的函數(shù)
- // 這個(gè)函數(shù)的作用就是,如果發(fā)現(xiàn)nextListeners,nextListeners指向同一個(gè)堆棧的話,就淺復(fù)制一份,這樣改nextListeners就不會(huì)改到currentListeners
- function ensureCanMutateNextListeners() {
- if (nextListeners === nextListeners) {
- nextListeners = currentListeners.slice()
- }
- }
- function subscribe(listener) {
- if (typeof listener !== 'function') {
- throw new Error('Expected listener to be a function.')
- }
- let isSubscribed = true
- ensureCanMutateNextListeners()
- // 直接將監(jiān)聽的函數(shù)放進(jìn)nextListeners里
- nextListeners.push(listener)
- return function unsubscribe() {
- // 如果已經(jīng)移除了就直接返回
- if (!isSubscribed) {
- return
- }
- isSubscribed = false
- ensureCanMutateNextListeners()
- // 沒有移除的話,先找到位置,通過splice移除
- const index = nextListeners.indexOf(listener)
- nextListeners.splice(index, 1)
- }
- }
在使用的時(shí)候就可以:
- const unsubscribe = store.subscribe(() =>
- console.log(store.getState())
- )
- unsubscribe()
- dispatch
dispatch
dispatch 作為一個(gè)重點(diǎn)函數(shù)~ 其實(shí)它的作用就是觸發(fā)狀態(tài)的改變。
參數(shù):action(object),它是一個(gè)描述發(fā)生了什么的對象,其中type是必須的屬性。
返回:這個(gè)傳入的object
- function dispatch(action) {
- if (!isPlainObject(action)) {
- throw new Error(
- 'Actions must be plain objects. ' +
- 'Use custom middleware for async actions.'
- )
- }
- //
- if (typeof action.type === 'undefined') {
- throw new Error(
- 'Actions may not have an undefined "type" property. ' +
- 'Have you misspelled a constant?'
- )
- }
- // 防止多次dispatch請求同時(shí)改狀態(tài),一定是前面的dispatch結(jié)束之后,才dispatch下一個(gè)
- if (isDispatching) {
- throw new Error('Reducers may not dispatch actions.')
- }
- try {
- isDispatching = true
- currentState = currentReducer(currentState, action)
- } finally {
- isDispatching = false
- }
- // 在dispatch的時(shí)候,又將nextListeners 賦值回currentListeners,
- const listeners = currentListeners = nextListeners
- for (let i = 0; i < listeners.length; i++) {
- const listener = listeners[i]
- listener()
- }
- return action
- }
在上面一系列完成之后,需要初始化appState的狀態(tài)。當(dāng)INIT action被dispatched 的時(shí)候,每個(gè)reducer都會(huì)return回它的初始狀態(tài)。
- dispatch({ type: ActionTypes.INIT })
自問自答環(huán)節(jié)
為什么createStore中既存在currentListeners也存在nextListeners?
在上面的源碼中,createStore函數(shù)為了保存store的訂閱者,不僅保存了當(dāng)前的訂閱者currentListeners而且也保存了nextListeners。createStore中有一個(gè)內(nèi)部函數(shù)ensureCanMutateNextListeners:
- function ensureCanMutateNextListeners() {
- if (nextListeners === currentListeners) {
- nextListeners = currentListeners.slice()
- }
- }
這個(gè)函數(shù)實(shí)質(zhì)的作用是確??梢愿淖僴extListeners,如果nextListeners與currentListeners一致的話,將currentListeners做一個(gè)拷貝賦值給nextListeners,然后所有的操作都會(huì)集中在nextListeners,比如我們看訂閱的函數(shù)subscribe:
- function subscribe(listener) {
- // ......
- let isSubscribed = true
- ensureCanMutateNextListeners()
- nextListeners.push(listener)
- return function unsubscribe() {
- // ......
- ensureCanMutateNextListeners()
- const index = nextListeners.indexOf(listener)
- nextListeners.splice(index, 1)
- }
我們發(fā)現(xiàn)訂閱和解除訂閱都是在nextListeners做的操作,然后每次dispatch一個(gè)action都會(huì)做如下的操作:
- function dispatch(action) {
- try {
- isDispatching = true
- currentState = currentReducer(currentState, action)
- } finally {
- isDispatching = false
- }
- // 相當(dāng)于currentListeners = nextListeners const listeners = currentListeners
- const listeners = currentListeners = nextListeners
- for (let i = 0; i < listeners.length; i++) {
- const listener = listeners[i]
- listener()
- }
- return action
- }
我們發(fā)現(xiàn)在dispatch中做了const listeners = currentListeners = nextListeners,相當(dāng)于更新了當(dāng)前currentListeners為nextListeners,然后通知訂閱者,到這里我們不禁要問為什么要存在這個(gè)nextListeners? 其實(shí)代碼中的注釋也是做了相關(guān)的解釋:
The subscriptions are snapshotted just before every dispatch() call.If you subscribe or unsubscribe while the listeners are being invoked, this will not have any effect on the dispatch() that is currently in progress.However, the next dispatch() call, whether nested or not, will use a more recent snapshot of the subscription list.
來讓我這個(gè)六級(jí)沒過的渣渣翻譯一下: 訂閱者(subscriptions)在每次dispatch()調(diào)用之前都是一份快照(snapshotted)。如果你在listener被調(diào)用期間,進(jìn)行訂閱或者退訂,在本次的dispatch()過程中是不會(huì)生效的,然而在下一次的dispatch()調(diào)用中,無論dispatch是否是嵌套調(diào)用的,都將使用最近一次的快照訂閱者列表。用圖表示的效果如下:
我們從這個(gè)圖中可以看見,如果不存在這個(gè)nextListeners這份快照的話,因?yàn)閐ispatch導(dǎo)致的store的改變,從而進(jìn)一步通知訂閱者,如果在通知訂閱者的過程中發(fā)生了其他的訂閱(subscribe)和退訂(unsubscribe),那肯定會(huì)發(fā)生錯(cuò)誤或者不確定性。例如:比如在通知訂閱的過程中,如果發(fā)生了退訂,那就既有可能成功退訂(在通知之前就執(zhí)行了nextListeners.splice(index, 1))或者沒有成功退訂(在已經(jīng)通知了之后才執(zhí)行了nextListeners.splice(index, 1)),這當(dāng)然是不行的。因?yàn)閚extListeners的存在所以通知訂閱者的行為是明確的,訂閱和退訂是不會(huì)影響到本次訂閱者通知的過程。
還是看不懂是什么意思?????一個(gè)簡單粗俗的例子:
當(dāng)在執(zhí)行這段代碼到第三個(gè)listener的時(shí)候:
- for (let i = 0; i < listeners.length; i++) {
- const listener = listeners[i]
- listener()
- }
你突然把第2個(gè)listener給splice了。這樣的話此時(shí)上面的循環(huán)本來是執(zhí)行完第三個(gè)要執(zhí)行第四個(gè)了,但是由于數(shù)組中的第2個(gè)listener被splice掉了,所以數(shù)組后面的元素都要往前移動(dòng)一個(gè)位置,這時(shí)數(shù)組的第四個(gè)listener就移動(dòng)到原先第三個(gè)的位置了,數(shù)組的第五個(gè)listener就移動(dòng)到原先第四個(gè)的位置了,因此循環(huán)本要執(zhí)行第四個(gè)的,結(jié)果由于第四個(gè)往前移動(dòng)了,實(shí)際執(zhí)行的是原先的第五個(gè),所以導(dǎo)致原先的第四個(gè)沒有被執(zhí)行。。
沒錯(cuò),上面講的就是這樣的!!!!哈哈哈明白了吧?。。。?/p>
但是這里又有一個(gè)問題了:
JavaScript不是單線程的嗎?為啥在執(zhí)行循環(huán)的時(shí)候,會(huì)執(zhí)行unsubscribe()操作
百思不得其解的情況下,去Redux項(xiàng)目下開了一個(gè)issue,得到了維護(hù)者的回答:
得了,我們再來看看測試相關(guān)的代碼吧。看完之后我了解到了。的確,因?yàn)镴avaScript是單線程語言,不可能出現(xiàn)出現(xiàn)想上述所說的多線程場景,但是我忽略了一點(diǎn),執(zhí)行訂閱者函數(shù)時(shí),在這個(gè)回調(diào)函數(shù)中可以執(zhí)行退訂或者訂閱事件。例如:
- const store = createStore(reducers.todos)
- const unsubscribe1 = store.subscribe(() => {
- const unsubscribe2 = store.subscribe(()=>{})
- })
這不就實(shí)現(xiàn)了在通知listener的過程中混入訂閱subscribe與退訂unsubscribe嗎?
為什么Reducer中不能進(jìn)行dispatch操作?
我們知道在reducer函數(shù)中是不能執(zhí)行dispatch操作的。一方面,reducer作為計(jì)算下一次state的純函數(shù)是不應(yīng)該承擔(dān)執(zhí)行dispatch這樣的操作。另一方面,即使你嘗試著在reducer中執(zhí)行dispatch,也并不會(huì)成功,并且會(huì)得到"Reducers may not dispatch actions."的提示。因?yàn)樵赿ispatch函數(shù)就做了相關(guān)的限制:
- function dispatch(action) {
- if (isDispatching) {
- throw new Error('Reducers may not dispatch actions.')
- }
- try {
- isDispatching = true
- currentState = currentReducer(currentState, action)
- } finally {
- isDispatching = false
- }
- //...notice listener
- }
在執(zhí)行dispatch時(shí)就會(huì)將標(biāo)志位isDispatching置為true。然后如果在currentReducer(currentState, action)執(zhí)行的過程中由執(zhí)行了dispatch,那么就會(huì)拋出錯(cuò)誤('Reducers may not dispatch actions.')。之所以做如此的限制,是因?yàn)樵赿ispatch中會(huì)引起reducer的執(zhí)行,如果此時(shí)reducer中又執(zhí)行了dispatch,這樣就落入了一個(gè)死循環(huán),所以就要避免reducer中執(zhí)行dispatch。
參考文獻(xiàn):
- https://github.com/MrErHu/blog/issues/18
- https://zhuanlan.zhihu.com/p/57316118
【編輯推薦】