redux之createStore

回顧一下 redux 的目錄結構:redux

.REDUXSRC
│ applyMiddleware.js
│ bindActionCreators.js
│ combineReducers.js
│ compose.js
│ createStore.js
│ index.js

└─utilssegmentfault

actionTypes.js
    isPlainObject.js
    warning.js

reduxindex.js 中一共暴露了5個 API, 上一篇文章講了下和 redux 關聯性不太大的 compose 。如今正式講一講最核心的 createStoreapi

createStore.js

createStore 大概是長成這個樣子的:數組

import $$observable from 'symbol-observable'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

export default function createStore(reducer, preloadedState, enhancer) {

    // 1. 對傳入參數的順序處理
    // 先忽略這一塊

    // 2. 變量的定義
    let currentReducer = reducer
    let currentState = preloadedState
    let currentListeners = []
    let nextListeners = currentListeners
    let isDispatching = false

    // 3. 一系列函數定義
    function ensuerCanMutateNextListeners(){}

    function getState(){}

    function subscribe(listener){}

    function dispatch(action){}

    function replaceReducer(nextReducer){}

    function observable(){}

    // 4. dispatch一個初始化的action
    dispatch({ type: ActionTypes.INIT })

    // 5. 返回store對象
    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
    }
}

咱們分別對這五塊來看看。閉包

1. 參數的順序處理

這一步就是對傳入給 createStore 的三個參數 reducerpreloadedStateenhancer 的順序調整。app

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    // 第二個參數是一個函數,沒有第三個參數的狀況
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
        // enhancer 不是函數就報錯
      throw new Error('Expected the enhancer to be a function.')
    }
    // enhancer就是高階函數,強化了自己這個createStore的函數,拿到加強後的createStore函數去處理
    // applyMiddleware這個函數還會涉及到這個

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
      // reducer不是函數報錯
    throw new Error('Expected the reducer to be a function.')
  }

  // 其餘代碼省略
}

2. 變量的定義

let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false
  • currentReducer 當前 storereducer,由 createStore 傳入的第一個參數 reducer 初始化
  • currentState 保存當前整個 state 的狀態,初始值就是 createStore 傳進來的第二個參數 preloadedState,至關於 store 的初始值
  • currentListeners 當前的監聽器,默認是空
  • nextListeners 下一個監聽器,由 currentListeners 賦值
  • isDispatching 當前的 store 是否正在 dispatch 一個action

全是閉包保存的變量dom

3. 函數的定義

createStore 的最後,dispatch 了一個 { type: ActionTypes.INIT } 對象,那就按圖索驥,從 dispatch 函數開始看。async

先把 ./utils 下的三個輔助函數(actionTypesisPlainObjectwarning)看一下:函數

actionTypes:this

const randomString = () =>
  Math.random()
    .toString(36)
    .substring(7)
    .split('')
    .join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

export default ActionTypes

這裏返回的都是隨機的 action.type,爲了區別常規業務開發寫的 action.type,好比:ActionTypes.INIT 拿到的是一個相似與 @@redux/INITg.f.m.0.0.4 隨機字符串,只有這樣奇奇怪怪的隨機數纔不會和業務中定義的 reducer 所判斷的 type 重複。


isPlainObject:

判斷函數是不是純對象,[1,23]new Date()這些都會返回 false

export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

warning:

就是一個報錯函數

export default function warning(message) {
  /* eslint-disable no-console */
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  /* eslint-enable no-console */
  try {
    // This error was thrown as a convenience so that if you enable
    // "break on all exceptions" in your console,
    // it would pause the execution at this line.
    throw new Error(message)
  } catch (e) {} // eslint-disable-line no-empty
}

dispatch

dispatch 用過 redux 的都知道,這就是派發 action 的函數,把派發出去的 action 交由 reducer 處理。

function dispatch(action) {
    if (!isPlainObject(action)) {
        // action不是純對象報錯
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
        // action沒有type屬性也報錯
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
        // 這個store正在dispach別的action的時候不能再dispatch另一個action
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
        // 當前state和action交由當前的reducer處理
        // 同時改變isDispatching 爲 true 代表正在處理action中,不能dispatch新的action了
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
        // 修改成 false ,能夠dispatch新的action
      isDispatching = false
    }

    // 賦值,最終 listeners 、 currentListeners 、nextListeners的值都是 nextListeners
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
        // 遍歷調用監聽的函數
      const listener = listeners[i]
      listener()
    }
    // 返回這個action, 沒什麼做用
    return action
}

核心代碼就是 currentState = currentReducer(currentState, action),傳入 currentStateactioncurrentReducercurrentReducer 把返回值賦值給了 currentState

subscribe

訂閱監聽器。

function subscribe(listener) {
    if (typeof listener !== 'function') {
        // 不給函數就報錯
      throw new Error('Expected the listener to be a function.')
    }

    if (isDispatching) {
        // 正在dispatch一個store的時候是不能訂閱監聽器的
      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#subscribe(listener) for more details.'
      )
    }

    // 給unsubscribe調用解除訂閱標識
    let isSubscribed = true

    // 下面解釋爲何要調用這個ensureCanMutateNextListeners函數
    ensureCanMutateNextListeners()
    // 就是簡單的把傳入的listeners放到nextListeners
    nextListeners.push(listener)

    // 返回一個解除訂閱的函數
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      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#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      // 從 nextListeners 數組中移除
      nextListeners.splice(index, 1)
    }
  }

訂閱沒什麼問題,就是爲啥用調用 ensureCanMutateNextListeners 呢?
看一下這個函數:

function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

這個函數就是檢查 nextListenerscurrentListeners 是不是相同的,若是是相同的就把 currentListeners 拷貝一個新的賦值給nextListeners。由於數組是引用類型的關係,若是 nextListenerscurrentListeners 相同,像 nextListenerspush 新的 listener 的時候會直接影響到 currentListeners 的值。

注意到另一點,在 dispatch 函數的最後遍歷 listeners 的時候,是這樣操做的: const listeners = (currentListeners = nextListeners),這裏 nextListenerscurrentListeners 就相同了。

那麼爲啥內部須要有 currentListenersnextListeners,主要是通知訂閱者的過程當中發生了其餘的訂閱(subscribe)和退訂(unsubscribe),那確定會發生錯誤或者不肯定性。

這裏有一篇文章論述到這個問題。

getState

簡單的把 storecurrentState 返回出來。

function getState() {
    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.'
      )
    }

    return currentState
  }

replaceReducer

這個 API 幫你替換把原來的 reducer 替換成新的 reducer

function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    // nextReducer替換舊的reducer
    currentReducer = nextReducer
    // 注意這裏也dispatch了一個隨機action,和createStore的最後dispatch一個隨機的初始化action功能是相同的,都是了初始化state
    dispatch({ type: ActionTypes.REPLACE })
  }

observable

不懂,仍是貼一下代碼:

function observable() {
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

4. dispatch一個初始化的action

dispatch({ type: ActionTypes.INIT })

在最後,dispatch 了一個 type 爲隨機值的 action, 咱們業務的 reducer 中最後沒有匹配到對用的 action.type 都會默認返回默認的 state, 而這個默認的 state 每每又在 reducer 函數最開始寫的時候已經給好了默認值,這樣 dispatchaction 與任何 reducer 都不匹配,因此拿到了全部 reducer 的默認值從而 currentState 就被更新成了 reducer 定義過的默認值。

5. 返回的store對象

把定義好的方法掛載到一個對象上面,這個對象就是 store 對象。

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

總結

redux 的代碼是真的簡潔,代碼的註釋甚至比代碼自己還要長,仍是很是值得閱讀的。

相關文章
相關標籤/搜索