史上最全的 Redux 源碼分析

前言

用 React + Redux 已經一段時間了,記得剛開始用Redux 的時候感受很是繞,總搞不起裏面的關係,若是你們用一段時間Redux又看了它的源碼話,對你的理解會有很大的幫助。看完後,在回來看Redux,有一種 柳暗花明又一村 的感受 ,.git

源碼

我分析的是用 es6 語法的源碼,你們看目錄結構,一共有 6 個問件。先說下各個文件大概功能。
圖片描述es6

  • applyMiddlewar.js 使用自定義的 middleware 來擴展 Reduxgithub

  • bindActionCreators.js 把 action creators 轉成擁有同名 keys 的對象,使用時能夠直接調用編程

  • combineReducers.js 一個比較大的應用,須要對 reducer 函數 進行拆分,拆分後的每一塊獨立負責管理 state 的一部分redux

  • compose.js 從右到左來組合多個函數,函數編程中經常使用到數組

  • createStore.js 建立一個 Redux Store 來放全部的stateapp

  • utils/warnimng.js 控制檯輸出一個警告,咱們能夠不用看異步

我會按每一個模塊的重要性,去作分析,今天就先把 createStore .js 分享給你們.async

createStore.js

註釋很詳細,直接看註釋就能夠了函數

// 導入 lodash ,判斷是不是普通(plain)對象
import isPlainObject from 'lodash/isPlainObject'
//導入 symbol 類型的 observable (symbol類型的屬性,是對象的私有屬性)
import $$observable from 'symbol-observable'

/**
 *定義 Redux Action 的初始化 type
 * 
 */
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

/**
 * 建立一個Redux store來存放應用全部的 state。應用中有且只有一個store
 *
 * @param {Function} reducer 是一個函數,接收兩個參數,分別是當前的 state 樹和
 * 要處理的 action,返回新的 state 樹
 *
 * @param {any} 初始化時的state ,在應用中,你能夠把服務端傳來通過處理後的 state
 *傳給它。若是你使用 combineReducers 建立 reducer,它必須是一個普通對象,與傳入
 *的 keys 保持一樣的結構。不然,你能夠自由傳入任何 reducer 可理解的內容。
 *
 * @param {Function} enhancer 是一個組合的高階函數,返回一個強化過的 store creator .
 *                  這與 middleware類似,它也容許你經過複合函數改變 store 接口。
 *
 * @returns {Store} 返回一個對象,給外部提供 dispatch, getState, subscribe, replaceReducer, 
 */
export default function createStore(reducer, preloadedState, enhancer) {

  //判斷 preloadedState 是一個函數而且 enhancer 是未定義 
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState  // 把 preloadedState 賦值給 enhancer
    preloadedState = undefined // 把 preloadedState 賦值爲 undefined
  }

  //判斷 enhancer 不是 undefined
  if (typeof enhancer !== 'undefined') {
    //判斷 enhancer 不是一個函數
    if (typeof enhancer !== 'function') {
      //拋出一個異常 (enhancer 必須是一個函數)
      throw new Error('Expected the enhancer to be a function.')
    }
    //調用 enhancer ,返回一個新強化過的 store creator
    return enhancer(createStore)(reducer, preloadedState)
  }
  
  //判斷 reducer 不是一個函數
  if (typeof reducer !== 'function') {
    //拋出一個異常 (reducer 必須是一個函數)
    throw new Error('Expected the reducer to be a function.')
  }

  
  var currentReducer = reducer        //把 reducer 賦值給 currentReducer
  var currentState = preloadedState   //把 preloadedState 賦值給 currentState
  var currentListeners = []           //初始化 listeners 數組
  var nextListeners = currentListeners//nextListeners 和 currentListeners 指向同一個引用
  var isDispatching = false           //標記正在進行dispatch

  /**
   * 保存一份訂閱快照
   * @return {void}
   */
  function ensureCanMutateNextListeners() {
    //判斷 nextListeners 和 currentListeners 是同一個引用
    if (nextListeners === currentListeners) {
      
      //經過數組的 slice 方法,複製出一個 listeners ,賦值給 nextListeners
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * 獲取 store 裏的當前 state tree
   *
   * @returns {any} 返回應用中當前的state tree
   */
  function getState() {

    //當前的state tree
    return currentState
  }

  /**
   *
   * 添加一個 listener . 當 dispatch action 的時候執行,這時 sate 已經發生了一些變化,
   * 你能夠在 listener 函數調用 getState() 方法獲取當前的 state
   *
   * 你能夠在 listener 改變的時候調用 dispatch ,要注意
   *
   * 1. 訂閱器(subscriptions) 在每次 dispatch() 調用以前都會保存一份快照。
   *    當你在正在調用監聽器(listener)的時候訂閱(subscribe)或者去掉訂閱(unsubscribe),
   *    對當前的 dispatch() 不會有任何影響。可是對於下一次的 dispatch(),不管嵌套與否,
   *    都會使用訂閱列表裏最近的一次快照。
   *
   * 2. 訂閱器不該該關注全部 state 的變化,在訂閱器被調用以前,每每因爲嵌套的 dispatch()
   *    致使 state 發生屢次的改變,咱們應該保證全部的監聽都註冊在 dispath() 以前。
   *
   * @param {Function} 要監聽的函數
   * @returns {Function} 一個能夠移除監聽的函數
   */
  function subscribe(listener) {
    //判斷 listener 不是一個函數
    if (typeof listener !== 'function') {

      //拋出一個異常 (listener 必須是一個函數)
      throw new Error('Expected listener to be a function.')
    }

    //標記有訂閱的 listener
    var isSubscribed = true

    //保存一份快照
    ensureCanMutateNextListeners()

    //添加一個訂閱函數
    nextListeners.push(listener)
    
    //返回一個取消訂閱的函數
    return function unsubscribe() {

      //判斷沒有訂閱一個 listener
      if (!isSubscribed) {

        //調用 unsubscribe 方法的時候,直接 return
        return
      }

      //標記如今沒有一個訂閱的 listener
      isSubscribed = false
      
      //保存一下訂閱快照
      ensureCanMutateNextListeners()
      //找到當前的 listener
      var index = nextListeners.indexOf(listener)
      //移除當前的 listener
      nextListeners.splice(index, 1)
    }
  }

  /**
   * dispath action。這是觸發 state 變化的唯一途徑。
   * 
   * @param {Object} 一個普通(plain)的對象,對象當中必須有 type 屬性
   *
   * @returns {Object} 返回 dispatch 的 action
   *
   * 注意: 若是你要用自定義的中間件, 它可能包裝 `dispatch()`
   *       返回一些其它東西,如( Promise )
   */
  function dispatch(action) {
    //判斷 action 不是普通對象。也就是說該對象由 Object 構造函數建立
    if (!isPlainObject(action)) {

      //拋出一個異常(actions 必須是一個普通對象. 或者用自定義的中間件來處理異步 actions)
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    // 判斷 action 對象的 type 屬性等於 undefined 
    if (typeof action.type === 'undefined') {

      //拋出一個異常
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }
  
    //判斷 dispahch 正在運行,Reducer在處理的時候又要執行 dispatch
    if (isDispatching) {
      
      //拋出一個異常(Reducer在處理的時候不能 dispatch action)
      throw new Error('Reducers may not dispatch actions.')
    }

    try {

      //標記 dispatch 正在運行
      isDispatching = true

      //執行當前 Reducer 函數返回新的 state
      currentState = currentReducer(currentState, action)

    } finally { // (try catch) 最終會被執行的地方

      //標記 dispatch 沒有在運行 
      isDispatching = false
    }

    //全部的的監聽函數賦值給 listeners
    var listeners = currentListeners = nextListeners

    //遍歷全部的監聽函數
    for (var i = 0; i < listeners.length; i++) {

      // 執行每個監聽函數
      listeners[i]()
    }

    //返回傳入的 action 對象
    return action
  }

  /**
   * 替換計算 state 的 reducer。
   *
   * 這是一個高級 API。
   * 只有在你須要實現代碼分隔,並且須要當即加載一些 reducer 的時候纔可能會用到它。
   * 在實現 Redux 熱加載機制的時候也可能會用到。
   *
   * @param {Function} store 要用的下一個 reducer.
   * @returns {void}
   */
  function replaceReducer(nextReducer) {

    //判斷 nextReducer 不是一個函數
    if (typeof nextReducer !== 'function') {

      //拋出一個異常 (nextReducer必須是一個函數)
      throw new Error('Expected the nextReducer to be a function.')
    }

    //當前傳入的 nextReducer 賦值給 currentReducer
    currentReducer = nextReducer

    //調用 dispatch 函數,傳入默認的 action
    dispatch({ type: ActionTypes.INIT })
  }

  /**
   *  在 creatorStore 內部沒有看到此方法的調用
   *  (猜測 : 做者可能想用比較強大,活躍的 observable 庫替換如今的 publish subscribe)
   *
   * @returns {observable} 狀態改變的時候返回最小的 observable.
   * 想要了解跟多關於 observable 庫,建議看下 
   * https://github.com/zenparsing/es-observable (標準 es Observable)
   */
  function observable() {
    //訂閱方法賦值給變量 outerSubscribe
    var outerSubscribe = subscribe
    return {
      /**
       * 這是一個最小的觀察訂閱方法
       * 
       * @param {Object}  觀察着的任何一個對象均可以做爲一個 observer.
       * 觀察者應該有 `next` 方法
       */
      subscribe(observer) {

        //判斷 observer 是一個對象
        if (typeof observer !== 'object') {
          //拋出異常
          throw new TypeError('Expected the observer to be an object.')
        }

        //獲取觀察着的狀態
        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        //返回一個取消訂閱的方法
        var unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },
      //對象的私有屬性,暫時不知道有什麼用途
      [$$observable]() {
        return this
      }
    }
  }

  //reducer 返回其初始狀態 
  //初始化 store 裏的 state tree
  dispatch({ type: ActionTypes.INIT })

  //返回 store 暴漏出的接口
  return {
    dispatch, //惟一一個能夠改變 state 的哈按時
    subscribe, //訂閱一個狀態改變後,要觸發的監聽函數 
    getState,  // 獲取 store 裏的 state
    replaceReducer, //Redux熱加載的時候能夠替換 Reducer
    [$$observable]: observable //對象的私有屬性,供內部使用
  }
}

結束語

代碼已經放在 githunb 上,你們能夠查看 https://github.com/jiechud/redux-source-analyze , 若是對你有幫助,麻煩請 Star 一下吧.

相關文章
相關標籤/搜索