redux源碼解讀--createStore源碼解析

createStore源碼解析

createStoreredux最核心的模塊。這個模塊就是用於建立一個store對象,同時,對外暴露出dispatch,getState,subscribereplaceReducer方法。(源碼中關於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方法執行到這行代碼的時候,listenerscurrentListenersnextListeners這三個變量引用內存中的同一份數組,只要其中一個發生變化,另外兩個立馬改變。和下面的這個例子同樣的含義:

圖片描述

因此,在這種狀況下。若是我在某個事件監聽器函數中調用了取消了某個監聽器,那麼在此次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源碼解讀倉庫

相關文章
相關標籤/搜索