您好,登錄后才能下訂單哦!
本篇內容主要講解“Redux源碼解析系列之什么是createStore”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Redux源碼解析系列之什么是createStore”吧!
INIT
這個方法是redux保留用的,用來初始化reducer的狀態
export const ActionTypes = { INIT: '@@redux/INIT' }
前面說 createStore的作用就是:創建一個store來管理app的狀態,唯一改變狀態的方式就是dispatch一個action,最終返回一個object。
return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }
不過replaceReducer,跟[$$observable]:都不常用~ ,所以這里只對前三的接口做解析。
createStore
在一個app里,只能有一個store,如果你想指明不同的state對應不同的action,可以用combineReducers去合并不同的reducer。
參數:
reducer(function):就是通過傳入當前State,還有action,計算出下一個state,返回回來。
preloadedState(any):initial state
enhancer(function):增強store的功能,讓它擁有第三方的功能,比如middleware.Redux里面唯一的enhancer就是applyMiddleware()
export default function createStore(reducer, preloadedState, enhancer) { // 第一段說的就是當第二個參數沒有傳preloadedState,而直接傳function的話,就會直接把這個function當成enhancer if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } // 當第三個參數傳了但是不是function也會報錯 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } //關鍵的一個就在于這里了,在前一篇講applyMiddleware的時候介紹了這么做的意義, //實際就是把createStore這件事在applyMiddleware里面做,轉移了鍋。 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 }
上面是第一個part,在校驗完參數的正確之后,終于可以干點正事兒了。createStore最終會返回一個Object.
{ dispatch, subscribe, getState }
接下來看看里面都做了什么:
getState
getState作用就是將當前state的狀態返回回來,沒啥好說的~
function getState() { return currentState }
subscribe
作用:添加監聽函數listener,它會在每次dispatch action的時候調用。
參數:listener(function): 在每一次dispatch action的時候都會調用的函數
返回:返回一個移除listener的函數
// 這個函數的作用就是,如果發現nextListeners,nextListeners指向同一個堆棧的話,就淺復制一份,這樣改nextListeners就不會改到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() // 直接將監聽的函數放進nextListeners里 nextListeners.push(listener) return function unsubscribe() { // 如果已經移除了就直接返回 if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() // 沒有移除的話,先找到位置,通過splice移除 const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
在使用的時候就可以:
const unsubscribe = store.subscribe(() => console.log(store.getState()) ) unsubscribe() dispatch
dispatch
dispatch 作為一個重點函數~ 其實它的作用就是觸發狀態的改變。
參數:action(object),它是一個描述發生了什么的對象,其中type是必須的屬性。
返回:這個傳入的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請求同時改狀態,一定是前面的dispatch結束之后,才dispatch下一個 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 在dispatch的時候,又將nextListeners 賦值回currentListeners, const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
在上面一系列完成之后,需要初始化appState的狀態。當INIT action被dispatched 的時候,每個reducer都會return回它的初始狀態。
dispatch({ type: ActionTypes.INIT })
自問自答環節
為什么createStore中既存在currentListeners也存在nextListeners?
在上面的源碼中,createStore函數為了保存store的訂閱者,不僅保存了當前的訂閱者currentListeners而且也保存了nextListeners。createStore中有一個內部函數ensureCanMutateNextListeners:
function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
這個函數實質的作用是確保可以改變nextListeners,如果nextListeners與currentListeners一致的話,將currentListeners做一個拷貝賦值給nextListeners,然后所有的操作都會集中在nextListeners,比如我們看訂閱的函數subscribe:
function subscribe(listener) { // ...... let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { // ...... ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) }
我們發現訂閱和解除訂閱都是在nextListeners做的操作,然后每次dispatch一個action都會做如下的操作:
function dispatch(action) { try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 相當于currentListeners = nextListeners const listeners = currentListeners const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
我們發現在dispatch中做了const listeners = currentListeners = nextListeners,相當于更新了當前currentListeners為nextListeners,然后通知訂閱者,到這里我們不禁要問為什么要存在這個nextListeners? 其實代碼中的注釋也是做了相關的解釋:
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.
來讓我這個六級沒過的渣渣翻譯一下: 訂閱者(subscriptions)在每次dispatch()調用之前都是一份快照(snapshotted)。如果你在listener被調用期間,進行訂閱或者退訂,在本次的dispatch()過程中是不會生效的,然而在下一次的dispatch()調用中,無論dispatch是否是嵌套調用的,都將使用最近一次的快照訂閱者列表。用圖表示的效果如下:
我們從這個圖中可以看見,如果不存在這個nextListeners這份快照的話,因為dispatch導致的store的改變,從而進一步通知訂閱者,如果在通知訂閱者的過程中發生了其他的訂閱(subscribe)和退訂(unsubscribe),那肯定會發生錯誤或者不確定性。例如:比如在通知訂閱的過程中,如果發生了退訂,那就既有可能成功退訂(在通知之前就執行了nextListeners.splice(index, 1))或者沒有成功退訂(在已經通知了之后才執行了nextListeners.splice(index, 1)),這當然是不行的。因為nextListeners的存在所以通知訂閱者的行為是明確的,訂閱和退訂是不會影響到本次訂閱者通知的過程。
還是看不懂是什么意思?????一個簡單粗俗的例子:
當在執行這段代碼到第三個listener的時候:
for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() }
你突然把第2個listener給splice了。這樣的話此時上面的循環本來是執行完第三個要執行第四個了,但是由于數組中的第2個listener被splice掉了,所以數組后面的元素都要往前移動一個位置,這時數組的第四個listener就移動到原先第三個的位置了,數組的第五個listener就移動到原先第四個的位置了,因此循環本要執行第四個的,結果由于第四個往前移動了,實際執行的是原先的第五個,所以導致原先的第四個沒有被執行。。
沒錯,上面講的就是這樣的!!!!哈哈哈明白了吧!!!!
但是這里又有一個問題了:
JavaScript不是單線程的嗎?為啥在執行循環的時候,會執行unsubscribe()操作
百思不得其解的情況下,去Redux項目下開了一個issue,得到了維護者的回答:
得了,我們再來看看測試相關的代碼吧。看完之后我了解到了。的確,因為JavaScript是單線程語言,不可能出現出現想上述所說的多線程場景,但是我忽略了一點,執行訂閱者函數時,在這個回調函數中可以執行退訂或者訂閱事件。例如:
const store = createStore(reducers.todos) const unsubscribe1 = store.subscribe(() => { const unsubscribe2 = store.subscribe(()=>{}) })
這不就實現了在通知listener的過程中混入訂閱subscribe與退訂unsubscribe嗎?
為什么Reducer中不能進行dispatch操作?
我們知道在reducer函數中是不能執行dispatch操作的。一方面,reducer作為計算下一次state的純函數是不應該承擔執行dispatch這樣的操作。另一方面,即使你嘗試著在reducer中執行dispatch,也并不會成功,并且會得到"Reducers may not dispatch actions."的提示。因為在dispatch函數就做了相關的限制:
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 }
在執行dispatch時就會將標志位isDispatching置為true。然后如果在currentReducer(currentState, action)執行的過程中由執行了dispatch,那么就會拋出錯誤('Reducers may not dispatch actions.')。之所以做如此的限制,是因為在dispatch中會引起reducer的執行,如果此時reducer中又執行了dispatch,這樣就落入了一個死循環,所以就要避免reducer中執行dispatch。
到此,相信大家對“Redux源碼解析系列之什么是createStore”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。