createStore
是redux
最核心的模塊。這個模塊就是用於建立一個store
對象,同時,對外暴露出dispatch
,getState
,subscribe
和replaceReducer
方法。(源碼中關於observable
的部分能夠忽略,這個是redux
內部使用的。咱們在開發中幾乎幾乎用不到)javascript
先看一下這個模塊的基本結構:java
依賴git
lodash/isPlainObject
symbol-observable
對外輸出github
dispatch
getState
subscribe
replaceReducer
[$$observable]
(幾乎不用)redux源碼中使用的
lodash/isPlainObject
依賴。在IE6-8
中性能不好,其實現方式和jQuery3.x
的實現類似,在舊版本的IE
中支持不了。最後會和你們一塊兒探討。redux
源碼註釋數組
// 判斷是否是純粹對象的模塊({}) import isPlainObject from 'lodash/isPlainObject' // 引入observable支持 import $$observable from 'symbol-observable'
export const ActionTypes = { INIT: '@@redux/INIT' }
上面這個是redux
內部使用的一個action
。主要用於內部測試和渲染初始的state。記住,咱們本身編寫action
的時候,action.type
不能爲@@redux/INIT
。由於,這個action會在redux
的內部自動調用。好比,下面的搗蛋代碼:瀏覽器
import {createStore, combineReducers, applyMiddleware} from '../src' import logger from 'redux-logger' const actionTypes = '@@redux/INIT' const reducers = (state = {}, action) => { switch(action.type) { case actionTypes: console.log('hello @@redux/INIT') return { 'type': actionTypes } default: return state } } const store = createStore(reducers, applyMiddleware(logger)) console.log('*************************************') console.log(store.getState()) //會渲染爲 {'type': '@@redux/INIT'} console.log('*************************************')
下面就是createStore
方法的實現:app
export default function createStore(reducer, preloadedState, enhancer){ //...初始條件的判斷和設定 function getState() { // getState方法的實現 } function subscribe() { // subscribe方法的實現 } function dispatch() { // dispatch方法的實現 } function replaceReducer() { // replaceReducer方法的實現 } function observable() { // observable方法的實現 } // store被建立後,自動分發一個'INIT' action。渲染出初始化的state樹。 dispatch({ type: ActionTypes.INIT }) }
下面就來一點點分析每一行代碼到底作什麼:async
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined }
在調用createStore
的方法的時候,能夠傳遞三個參數createStore(reducer, preloadedState, enhancer)
。其中,各參數屬性以下:函數
reducer
必需參數,function
類型preloadedState
可選參數,object
類型enhancer
可選參數,function
類型在日常的使用中,咱們通常會省略第二個參數。好比,當咱們須要使用redux中間件的時候
,就會像第三個參數傳遞一個applyMiddleware()
[返回值是一個function
]。若是,咱們沒有初始狀態,則會省略第二個參數。這個時候,咱們的函數調用形式爲:
const store = createStore(reducer, applyMiddleware(...))
這個時候就會執行上面源碼中的代碼,使函數調用知足最基本的形式調用。也就是函數在傳遞兩個或者三個參數的狀況下,其內部處理邏輯都是同樣的。
// 若是咱們指定了reducer加強器enhancer if (typeof enhancer !== 'undefined') { // enhancer必須是一個函數 if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // 這個函數接收createStore做爲參數,而且返回一個函數,這個函數接收的參數是reducer,preloadedState // 直接返回通過enhancer包裝的對象 return enhancer(createStore)(reducer, preloadedState) }
想更好的理解這段代碼,能夠參考applyMiddleware內部的實現。
// 要求傳遞給createStore的第一個參數必須是一個函數 if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') }
// 保存初始的reducer let currentReducer = reducer // 保存初始的state let currentState = preloadedState // 保存全部的事件監聽器 let currentListeners = [] // 獲取當前監聽器的一個副本(相同的引用) let nextListeners = currentListeners // 是否正在派發action let isDispatching = false function ensureCanMutateNextListeners() { // 若是nextListeners和currentListeners具備相同的引用,則獲取一份當前事件監聽器集合的一個副本保存到nextListeners中 if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
上面就是createStore
方法中的一些初始參數,這裏有一個地方值得思考:爲何要維護兩份事件監聽器列表(nextListeners,currentListeners)?。下面,咱們會解釋。
// 直接返回當前store的state function getState() { return currentState }
function subscribe(listener) { // 事件監聽器必須是函數,不然會拋出異常 if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.') } // 這個事件監聽器是否已經被取消的標誌(我的感受:這個初始值應該被設置爲false,語意化更好一些。) let isSubscribed = true // 調用這個函數的結果就是生成一份當前事件監聽器的一個副本保存到nextListeners中 ensureCanMutateNextListeners() // 將新的事件監聽器添加到nextListeners中 nextListeners.push(listener) // 返回一個取消監聽的函數 return function unsubscribe() { // 若是這個監聽器已經被取消了,則直接return if (!isSubscribed) { return } // 將監聽器是否取消的標誌設置爲false isSubscribed = false // 再次生成一份事件監聽器集合的副本 ensureCanMutateNextListeners() // 獲取到須要取消的事件監聽器的索引 const index = nextListeners.indexOf(listener) // 從事件監聽器集合中刪除這個事件監聽器 nextListeners.splice(index, 1) } }
從subscribe
方法的源碼中能夠看出,每次在進行監聽器的添加/刪除
以前,都會基於當前的監聽器集合生成一個副本保存到nextListeners
中。這個時候仍是不能準確的回答上面的問題,下面咱們繼續研究dispatch
的源碼:
function dispatch(action) { // dispatch的參數就是咱們須要派發的action,必定要保證這個action是一個純粹的對象 // 若是不是一個純粹的對象,則會拋出異常。 if (!isPlainObject(action)) { // 這個方法有坑,在低版本的IE瀏覽器中性能不好,最後咱們會研究這個方法的源碼。 throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } // 所派發的action必須有一個type屬性(咱們能夠將這個屬性認爲就是action的身份證,這樣redux才知道你派發的是哪一個action,你須要作什麼,該怎麼爲你作) // 若是沒有這個屬性則會拋出異常 if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } // 若是redux正在派發action,則拋出異常?何時會出現這種狀況??? if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true // 派發action // 實質就是將當前的state和你須要派發的action傳遞給reducer函數病返回一個新的state currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 這一塊也是一個十分關鍵的地方,哈哈哈哈哈,又多了一份事件監聽器的列表,簡單的說一下這三份列表的做用 // nextListeners: 保存此次dispatch後,須要觸發的全部事件監聽器的列表 // currentListeners: 保存一份nextListeners列表的副本 // listeners: 須要執行的列表 const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] // 調用全部的事件監聽器 listener() } // dispatch的返回值也是十分重要的,若是沒有這個返回值,就不可能引入強大的中間件機制。 return action }
到這裏,咱們就能夠回答這個問題了:爲何要維護兩份事件監聽器列表(nextListeners,currentListeners)?
首先,咱們必需要知道的事情就是:咱們的監聽器在何時會執行?在咱們的調用dispatch派發action以後。ok,看下面的這個圖:
這個圖表示,當dispatch
方法執行到這行代碼的時候,listeners
,currentListeners
,nextListeners
這三個變量引用內存中的同一份數組,只要其中一個發生變化,另外兩個立馬改變。和下面的這個例子同樣的含義:
因此,在這種狀況下。若是我在某個事件監聽器函數中調用了取消了某個監聽器,那麼在此次dispatch後,被取消的這個事件監聽器就不會被執行了(?????是嗎????)。
import {createStore, combineReducers, applyMiddleware} from '../src' import logger from 'redux-logger' const actionTypes = '@@redux/INIT' const reducers = (state = {}, action) => { switch(action.type) { case actionTypes: return { 'type': actionTypes } default: return state } } const store = createStore(reducers, applyMiddleware(logger)) const listener1 = store.subscribe(() => { console.log('listener1') }) const listener2 = store.subscribe(() => { // 取消listener3 listener3() console.log('listener2') }) const listener3 = store.subscribe(() => { console.log('listener3') }) store.dispatch({type: actionTypes})
結果是:
listener1 listener2 listener3
結果,就是:即便你在某個事件監聽器中,取消了其它的事件監聽器,那麼被取消的這個事件監聽器,在此次dispatch後仍然會執行。也就是說。redux會保證在某個dispatch後,會保證在這個dispatch以前的全部事件監聽器所有執行。
這是個bug仍是個feature。無從而知,可是從redux源碼中,能夠知道,這是一個bug。因此,redux做者就利用上面的方法很巧妙的避免了這種狀況。其實實現的方法很簡單:切斷nextListeners和currentListener,listeners相同的引用關係。
下面接着扯:
// 提換reducer的方法。(動態加載reducers的時候才用) function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer // 替換結束後,從新初始化 dispatch({ type: ActionTypes.INIT }) }
// 觸發預設action,主要就是爲了生成初始的state tree的結構 dispatch({ type: ActionTypes.INIT })
// 這就很熟悉了吧 return { dispatch, subscribe, getState, replaceReducer, // 尼瑪 忽略這個 [$$observable]: observable }
這就是對createStore
源碼的一個總體解讀,水平有限,歡迎拍磚。後續的源碼解讀和測試例子能夠關注:redux源碼解讀倉庫