Redux 源碼解讀 —— 從源碼開始學 Redux

已經快一年沒有碰過 React 全家桶了,最近換了個項目組要用到 React 技術棧,因此最近又複習了一下;撿起舊知識的同時又有了一些新的收穫,在這裏做文以記之。webpack

在閱讀文章以前,最好已經知道如何使用 Redux(不是 React-Redux)。git

1、準備環境

爲了更好的解讀源碼,咱們能夠把源碼拷貝到本地,而後搭建一個開發環境。Redux 的使用不依賴於 React,因此你徹底能夠在一個極爲簡單的 JavaScript 項目中使用它。這裏再也不贅述開發環境的搭建過程,須要的同窗能夠直接拷貝個人代碼到本地,而後安裝依賴,運行項目。github

$ git clone https://github.com/zhongdeming428/redux && cd redux

$ npm i

$ npm run dev
複製代碼

2、閱讀源碼

(1)源代碼結構

忽略項目中的那些說明文檔什麼的,只看 src 這個源文件目錄,其結構以下:web

src
├── applyMiddleware.js  // 應用中間件的 API
├── bindActionCreators.js   // 轉換 actionCreators 的 API
├── combineReducers.js  // 組合轉換 reducer 的 API
├── compose.js  // 工具函數,用於嵌套調用中間件
├── createStore.js  // 入口函數,建立 store 的 API
├── index.js    // redux 項目的入口文件,用於統一暴露全部 API
├── test
│   └── index.js    // 我所建立的用於調試的腳本
└── utils   // 專門放工具函數的目錄
    ├── actionTypes.js  // 定義了一些 redux 預留的 action type
    ├── isPlainObject.js  // 用於判斷是不是純對象 
    └── warning.js  // 用於拋出合適的警告信息

複製代碼

能夠看出來 redux 的源碼結構簡單清晰明瞭,幾個主要的(也是僅有的) API 被儘量的分散到了單個的文件模塊中,咱們只須要挨個的看就好了。npm

(2)index.js

上一小節說到 index.js 是 redux 項目的入口文件,用於暴露全部的 API,因此咱們來看看代碼:redux

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
// 不一樣的 API 寫在不一樣的 js 文件中,最後經過 index.js 統一導出。

// 這個函數用於判斷當前代碼是否已經被打包工具(好比 Webpack)壓縮過,若是被壓縮過的話,
// isCrushed 函數的名稱會被替換掉。若是被替換了函數名可是 process.env.NODE_ENV 又不等於 production
// 的時候,提醒用戶使用生產環境下的精簡代碼。
function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === "production". ' +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}

// 導出主要的 API。
export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}
複製代碼

我刪除了全部的英文註釋以減少篇幅,若是你們想看原來的註釋,能夠去 redux 的項目查看源碼。api

能夠看到在程序的頭部引入了全部的 API 模塊以及工具函數,而後在底部統一導出了。這一部分比較簡單,主要是 isCrushed 函數有點意思。做者用這個函數來判斷代碼是否被壓縮過(判斷函數名是否被替換掉了)。數組

這一部分也引用到了工具函數,因爲這幾個函數比較簡單,因此能夠先看看它們是幹嗎的。promise

(3)工具函數

除了 compose 函數之外,全部的工具函數都被放在了 utils 目錄下。緩存

actionTypes.js

這個工具模塊定義了幾種 redux 預留的 action type,包括 reducer 替換類型、reducer 初始化類型和隨機類型。下面上源碼:

// 定義了一些 redux 保留的 action type。
// 隨機字符串確保惟一性。
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
複製代碼

能夠看出就是返回了一個 ActionTypes 對象,裏面包含三種類型:INIT、REPLACE 和 PROBE_UNKNOW_ACTION。分別對應以前所說的幾種類型,爲了防止和用戶自定義的 action type 相沖突,刻意在 type 裏面加入了隨機值。在後面的使用中,經過引入 ActionType 對象來進行對比。

isPlainObject.js

這個函數用於判斷傳入的對象是不是純對象,由於 redux 要求 action 和 state 是一個純對象,因此這個函數誕生了。

上源碼:

/** * 判斷一個參數是不是純對象,純對象的定義就是它的構造函數爲 Object。 * 好比: { name: 'isPlainObject', type: 'funciton' }。 * 而 isPlainObject 這個函數就不是純對象,由於它的構造函數是 Function。 * @param {any} obj 要檢查的對象。 * @returns {boolean} 返回的檢查結果,true 表明是純對象。 */
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.js

這個函數用於拋出適當的警告,沒啥好說的。

/** * Prints a warning in the console if it exists. * * @param {String} message The warning message. * @returns {void} */
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
}
複製代碼

compose.js

這個函數用於嵌套調用中間件(middleware),進行初始化。

/** * 傳入一系列的單參數函數做爲參數(funcs 數組),返回一個新的函數,這個函數能夠接受 * 多個參數,運行時會將 funcs 數組中的函數從右至左進行調用。 * @param {...Function} funcs 一系列中間件。 * @returns {Function} 返回的結果函數。 * 從右至左調用,好比: compose(f, g, h) 將會返回一個新函數 * (...args) => f(g(h(...args))). */
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }
  // 經過 reduce 方法迭代。
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼

(4)createStore.js

看完了工具函數和入口函數,接下來就要正式步入主題了。咱們使用 redux 的重要一步就是經過 createStore 方法建立 store。那麼接下來看看這個方法是怎麼建立 store 的,store 又是個什麼東西呢?

咱們看源碼:

import $$observable from 'symbol-observable'
// 後面會講。
import ActionTypes from './utils/actionTypes'
// 引入一些預約義的保留的 action type。
import isPlainObject from './utils/isPlainObject'
// 判斷一個對象是不是純對象。

// 使用 redux 最主要的 API,就是這個 createStore,它用於建立一個 redux store,爲你提供狀態管理。
// 它接受三個參數(第二三個可選),第一個是 reducer,用於改變 redux store 的狀態;第二個是初始化的 store,
// 即最開始時候 store 的快照;第三個參數是由 applyMiddleware 函數返回的 enhancer 對象,使用中間件必須
// 提供的參數。
export default function createStore(reducer, preloadedState, enhancer) {
  // 下面這一段基本能夠不看,它們是對參數進行適配的。
  /*************************************參數適配****************************************/
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    // 若是傳遞了多個 enhancer,拋出錯誤。
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function'
    )
  }
  // 若是沒有傳遞默認的 state(preloadedState 爲函數類型,enhancer 爲未定義類型),那麼傳遞的
  // 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 進行處理,引入中間件。注意此處沒有再傳遞 enhancer 做爲參數。實際上 enhancer 會對 createStore 進行處理,而後返回一個實際意義上的 createStore 用於建立 store 對象,參考 applyMiddleware.js。
    return enhancer(createStore)(reducer, preloadedState)
  }
  // 若是 reducer 不是函數類型,報錯。
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
  /*********************************************************************************/

  // 在函數內部定義一系列局部變量,用於存儲數據。
  let currentReducer = reducer  // 存儲當前的 reducer。
  let currentState = preloadedState // 用於存儲當前的 store,即爲 state。 
  let currentListeners = [] // 用於存儲經過 store.subscribe 註冊的當前的全部訂閱者。
  let nextListeners = currentListeners  // 新的 listeners 數組,確保不直接修改 listeners。
  let isDispatching = false // 當前狀態,防止 reducer 嵌套調用。

  // 顧名思義,確保 nextListeners 能夠被修改,當 nextListeners 與 currentListeners 指向同一個數組的時候
  // 讓 nextListeners 成爲 currentListeners 的副本。防止修改 nextListeners 致使 currentListeners 發生變化。
  // 一開始我也不是很明白爲何會存在 nextListeners,由於後面 dispatch 函數中仍是直接把 nextListeners 賦值給了 currentListeners。
  // 直接使用 currentListeners 也是能夠的。後來去 redux 的 repo 搜了搜,發現了一個 issue(https://github.com/reduxjs/redux/issues/2157) 講述了這個作法的理由。
  // 提交這段代碼的做者的解釋(https://github.com/reduxjs/redux/commit/c031c0a8d900e0e95a4915ecc0f96c6fe2d6e92b)是防止 Array.slice 的濫用,只有在必要的時候調用 Array.slice 方法來複制 listeners。
  // 之前的作法是每次 dispatch 都要 slice 一次,致使了性能的下降吧。
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 返回 currentState,即 store 的快照。
  function getState() {
    // 防止在 reducer 中調用該方法,reducer 會接受 state 參數。
    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
  }

  // store.subscribe 函數,訂閱 dispatch。
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
    // 不容許在 reducer 中進行訂閱。
    if (isDispatching) {
      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.'
      )
    }

    let isSubscribed = true
    // 每次操做 nextListeners 以前先確保能夠修改。
    ensureCanMutateNextListeners()
    // 存儲訂閱者的註冊方法。
    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
      // 每次操做 nextListeners 以前先確保能夠修改。
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  // store.dispatch 函數,用於觸發 reducer 修改 state。
  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?'
      )
    }
    // reducer 內部不容許派發 action。
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      // 調用 reducer 以前,先將標誌位置一。
      isDispatching = true
      // 調用 reducer,返回的值即爲最新的 state。
      currentState = currentReducer(currentState, action)
    } finally {
      // 調用完以後將標誌位置 0,表示 dispatch 結束。
      isDispatching = false
    }

    // dispatch 結束以後,執行全部訂閱者的函數。
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    // 返回當前所使用的 action,這一步是中間件嵌套使用的關鍵,很重要。
    return action
  }

  // 一個比較新的 API,用於動態替換當前的 reducers。適用於按需加載,代碼拆分等場景。
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }
    // 執行默認的 REPLACE 類型的 action。在 combineReducers 函數中有使用到這個類型。
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }

  // 這是爲了適配 ECMA TC39 會議的一個有關 Observable 的提案(參考https://github.com/tc39/proposal-observable)所寫的一個函數。
  // 做用是訂閱 store 的變化,適用於全部實現了 Observable 的類庫(主要是適配 RxJS)。
  // 我找到了引入這個功能的那個 commit:https://github.com/reduxjs/redux/pull/1632。
  function observable() {
    // outerSubscribe 即爲外部的 subscribe 函數。
    const outerSubscribe = subscribe
    // 返回一個純對象,包含 subscribe 方法。
    return {
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

        // 用於給 subscribe 註冊的函數,嚴格按照 Observable 的規範實現,observer 必須有一個 next 屬性。
        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

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

      // $$observable 即爲 Symbol.observable,也屬於 Observable 的規範,返回自身。
      [$$observable]() {
        return this
      }
    }
  }
  // 初始化時 dispatch 一個 INIT 類型的 action,校驗各類狀況。
  dispatch({ type: ActionTypes.INIT })

  // 返回一個 store 對象。
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
複製代碼

不難發現,咱們的 store 對象就是一個純 JavaScript 對象。包含幾個屬性 API,而咱們的 state 就存儲在 createStore 這個方法內部,是一個局部變量,只能經過 getState 方法訪問到。這裏其實是對閉包的利用,全部咱們操做的 state 都存儲在 getState 方法內部的一個變量裏面。直到咱們的程序結束(或者說 store 被銷燬),createStore 方法纔會被回收,裏面的變量纔會被銷燬。

而 subscribe 方法就是對觀察者模式的利用(注意不是發佈訂閱模式,兩者有區別,不要混淆),咱們經過 subscribe 方法註冊咱們的函數,咱們的函數會給存儲到 createStore 方法的一個局部變量當中,每次 dispatch 被調用以後,都會遍歷一遍 currentListeners,依次執行其中的方法,達到咱們訂閱的要求。

(5)combineReducers.js

瞭解了 createStore 究竟是怎麼一回事,咱們再來看看 combineReducers 究竟是怎麼建立 reducer 的。

咱們寫 reducer 的時候,其實是在寫一系列函數,而後整個到一個對象的屬性上,最後傳給 combineReducers 進行處理,處理以後就能夠供 createStore 使用了。

例如:

// 建立咱們的 reducers。
const _reducers = {
  items(items = [], { type, payload }) {
    if (type === 'ADD_ITEMS') items.push(payload);
    return items;
  },
  isLoading(isLoading = false, { type, payload }) {
    if (type === 'IS_LOADING') return true;
    return false;
  }
};
// 交給 combineReducers 處理,適配 createStore。
const reducers = combineReducers(_reducers);
// createStore 接受 reducers,建立咱們須要的 store。
const store = createStore(reducers);
複製代碼

那麼 combineReducers 對咱們的 reducers 對象進行了哪些處理呢?

下面的代碼比較長,但願你們能有耐心。

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

/** * 用於獲取錯誤信息的工具函數,若是調用你所定義的某個 reducer 返回了 undefined,那麼就調用這個函數 * 拋出合適的錯誤信息。 * * @param {String} key 你所定義的某個 reducer 的函數名,同時也是 state 的一個屬性名。 * * @param {Object} action 調用 reducer 時所使用的 action。 */
function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionDescription =
    (actionType && `action "${String(actionType)}"`) || 'an action'

  return (
    `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
    `To ignore an action, you must explicitly return the previous state. ` +
    `If you want this reducer to hold no value, you can return null instead of undefined.`
  )
}

/** * 工具函數,用於校驗未知鍵,若是 state 中的某個屬性沒有對應的 reducer,那麼返回報錯信息。 * 對於 REPLACE 類型的 action type,則不進行校驗。 * @param {Object} inputState * @param {Object} reducers * @param {Object} action * @param {Object} unexpectedKeyCache */
function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) {
  const reducerKeys = Object.keys(reducers)
  const argumentName =
    action && action.type === ActionTypes.INIT
      ? 'preloadedState argument passed to createStore'
      : 'previous state received by the reducer'

  // 若是 reducers 長度爲 0,返回對應錯誤信息。
  if (reducerKeys.length === 0) {
    return (
      'Store does not have a valid reducer. Make sure the argument passed ' +
      'to combineReducers is an object whose values are reducers.'
    )
  }

  if (!isPlainObject(inputState)) {
    return (
      `The ${argumentName} has unexpected type of "` +
      {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +             // {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1]
      `". Expected argument to be an object with the following ` +          // 返回的是 inputState 的類型。
      `keys: "${reducerKeys.join('", "')}"`
    )
  }

  // 獲取全部 State 有而 reducers 沒有的屬性,加入到 unexpectedKeysCache。
  const unexpectedKeys = Object.keys(inputState).filter(
    key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  )

  // 加入到 unexpectedKeyCache。
  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })

  // 若是是 REPLACE 類型的 action type,再也不校驗未知鍵,由於按需加載的 reducers 不須要校驗未知鍵,如今不存在的 reducers 可能下次就加上了。
  if (action && action.type === ActionTypes.REPLACE) return

  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      `Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    )
  }
}

/** * 用於校驗全部 reducer 的合理性:傳入任意值都不能返回 undefined。 * @param {Object} reducers 你所定義的 reducers 對象。 */
function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    // 獲取初始化時的 state。
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
          `If the state passed to the reducer is undefined, you must ` +
          `explicitly return the initial state. The initial state may ` +
          `not be undefined. If you don't want to set a value for this reducer, ` +
          `you can use null instead of undefined.`
      )
    }
    // 若是初始化校驗經過了,有多是你定義了 ActionTypes.INIT 的操做。這時候從新用隨機值校驗。
    // 若是返回 undefined,說明用戶可能對 INIT type 作了對應處理,這是不容許的。
    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === 'undefined'
    ) {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
          `Don't try to handle ${ ActionTypes.INIT } or other actions in "redux/*" ` +
          `namespace. They are considered private. Instead, you must return the ` +
          `current state for any unknown actions, unless it is undefined, ` +
          `in which case you must return the initial state, regardless of the ` +
          `action type. The initial state may not be undefined, but can be null.`
      )
    }
  })
}

// 把你所定義的 reducers 對象轉化爲一個龐大的彙總函數。
// 能夠看出,combineReducers 接受一個 reducers 對象做爲參數,
// 而後返回一個總的函數,做爲最終的合法的 reducer,這個 reducer 
// 接受 action 做爲參數,根據 action 的類型遍歷調用全部的 reducer。
export default function combineReducers(reducers) {
  // 獲取 reducers 全部的屬性名。
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  // 遍歷 reducers 的全部屬性,剔除全部不合法的 reducer。
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      // 將 reducers 中的全部 reducer 拷貝到新的 finalReducers 對象上。
      finalReducers[key] = reducers[key]
    }
  }
  // finalReducers 是一個純淨的通過過濾的 reducers 了,從新獲取全部屬性名。
  const finalReducerKeys = Object.keys(finalReducers)

  let unexpectedKeyCache
  // unexpectedKeyCache 包含全部 state 中有可是 reducers 中沒有的屬性。
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    // 校驗全部 reducer 的合理性,緩存錯誤。
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  // 這就是返回的新的 reducer,一個純函數。每次 dispatch 一個 action,都要執行一遍 combination 函數,
  // 進而把你所定義的全部 reducer 都執行一遍。
  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      // 若是有緩存的錯誤,拋出。
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      // 非生產環境下校驗 state 中的屬性是否都有對應的 reducer。
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    // state 是否改變的標誌位。
    const nextState = {}
    // reducer 返回的新 state。
    for (let i = 0; i < finalReducerKeys.length; i++) {   // 遍歷全部的 reducer。
      const key = finalReducerKeys[i]  // 獲取 reducer 名稱。
      const reducer = finalReducers[key]  // 獲取 reducer。
      const previousStateForKey = state[key]  // 舊的 state 值。
      const nextStateForKey = reducer(previousStateForKey, action)  // 執行 reducer 返回的新的 state[key] 值。
      if (typeof nextStateForKey === 'undefined') {
        // 若是通過了那麼多校驗,你的 reducer 仍是返回了 undefined,那麼就要拋出錯誤信息了。
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      // 把返回的新值添加到 nextState 對象上,這裏能夠看出來,你所定義的 reducer 的名稱就是對應的 state 的屬性,因此 reducer 命名要規範!
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
      // 檢驗 state 是否發生了變化。
    }
    // 根據標誌位返回對應的 state。
    return hasChanged ? nextState : state
  }
}
複製代碼

(6)applyMiddleware.js

combineReducers 方法代碼比較多,可是實際邏輯仍是很簡單的,接下來這個函數代碼很少,可是邏輯要稍微複雜一點,它就是應用中間件的 applyMiddleware 函數。這個函數的思想比較巧妙,值得學習。

import compose from './compose'

//&emsp;用於應用中間件的函數,能夠同時傳遞多箇中間件。中間件的標準形式爲:
// const middleware = store => next => action => { /*.....*/ return next(action); }
export default function applyMiddleware(...middlewares) {
  //&emsp;返回一個函數,接受&emsp;createStore&emsp;做爲參數。args 參數即爲 reducer 和 preloadedState。
  return createStore => (...args) => {
    // 在函數內部調用 createStore 建立一個&emsp;store 對象,這裏不會傳遞 enhancer,由於 applyMiddleware 自己就是在建立一個 enhancer,而後給 createStore 調用。
    // 這裏其實是經過 applyMiddleware 把 store 的建立推遲了。爲何要推遲呢?由於要利用 middleWares 作文章,先初始化中間件,從新定義 dispatch,而後再建立 store,這時候建立的 store 所包含的 dispatch 方法就區別於不傳遞 enhancer 時所建立的 dispatch 方法了,其中包含了中間件所定義的一些邏輯,這就是爲何中間件能夠干預 dispatch 的緣由。
    const store = createStore(...args)
    // 這裏對 dispatch 進行了從新定義,無論傳入什麼參數,都會報錯,這樣作的目的是防止你的中間件在初始化的時候就
    // 調用 dispatch。
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args) // 注意最後 dispatch 的時候不會訪問上面報錯的那個 dispatch 函數了,由於那個函數被下面的 dispatch 覆蓋了。
    }
    // 對於每個 middleware,都傳入 middlewareAPI 進行調用,這就是中間件的初始化。
    // 初始化後的中間件返回一個新的函數,這個函數接受 store.dispatch 做爲參數,返回一個替換後的 dispatch,做爲新的
    // store.dispatch。
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose 方法把全部中間件串聯起來調用。用最終結果替換 dispatch 函數,以後所使用的全部 store.dispatch 方法都已是
    // 替換了的,加入了新的邏輯。
    dispatch = compose(...chain)(store.dispatch)
    // 初始化中間件之後,把報錯的 dispatch 函數覆蓋掉。

    /** * middle 的標準形式: * const middleware = ({ getState, dispatch }) => next => action => { * // .... * return next(action); * } * 這裏 next 是通過上一個 middleware 處理了的 dispatch 方法。 * next(action) 返回的仍然是一個 dispatch 方法。 */

    return {
      ...store,
      dispatch  // 全新的 dispatch。
    }
  }
}
複製代碼

代碼量真的不多,可是真的很巧妙,這裏有幾點很關鍵:

  • compose 方法利用 Array.prototype.reduce 實現中間件的嵌套調用,返回一個全新的函數,能夠接受新的參數(上一個中間件處理過的 dispatch),最終返回一個全新的包含新邏輯的 dispatch 方法。你看 middleware 通過初始化後返回的函數的格式:

    next => action => {
      return next(action);
    }
    複製代碼

    其中 next 能夠當作 dispatch,這不就是接受一個 dispatch 做爲參數,而後返回一個新的 dispatch 方法嗎?緣由就是咱們能夠認爲接受 action 做爲參數,而後觸發 reducer 更改 state 的全部函數都是 dispatch 函數。

  • middleware 中間件通過初始化之後,返回一個新函數,它接受 dispatch 做爲參數,而後返回一個新的 dispatch 又能夠供下一個 middleware 調用,這就致使全部的 middleware 能夠嵌套調用了!並且最終返回的結果也是一個 dispatch 函數。

    最終獲得的 dispatch 方法,是把原始的 store.dispatch 方法傳遞給最後一個 middleware,而後層層嵌套處理,最後通過第一個 middleware 處理過之後所返回的方法。因此咱們在調用應用了中間件的 dispatch 函數時,從左至右的通過了 applyMiddleware 方法的全部參數(middleware)的處理。這有點像是包裹和拆包裹的過程。

  • 爲何 action 能夠通過全部的中間件處理呢?咱們再來看看中間件的基本結構:

    ({ dispatch, getState }) => next => action => {
      return next(action);
    }
    複製代碼

    咱們能夠看到 action 進入函數之後,會通過 next 的處理,而且會返回結果。next 會返回什麼呢?由於第一個 next 的值就是 store.dispatch,因此看看 store.dispatch 的源碼就知道了。

    function dispatch(action) {
      // 省略了一系列操做的代碼……
    
      // 返回當前所使用的 action,這一步是中間件嵌套使用的關鍵。
      return action
    }
    複製代碼

    沒錯,store.dispatch 最終返回了 action,因爲中間件嵌套調用,因此每一個 next 都返回 action,而後又能夠供下一個 next 使用,環環相扣,十分巧妙。

這部分描述的有點拗口,語言捉急但又不想畫圖,各位仍是本身多想一想好了。

(7)bindActionCreators.js

這個方法沒有太多好說的,主要做用是減小你們 dispatch reducer 所要寫的代碼,好比你原來有一個 action:

const addItems = item => ({
  type: 'ADD_ITEMS',
  payload: item
});
複製代碼

而後你要調用它的時候:

store.dispatch(addItems('item value'));
複製代碼

若是你使用 bindActionCreators:

const _addItems = bindActionCreators(addItems, store.dispatch);
複製代碼

當你要 dispatch reducer 的時候:

_addItems('item value');
複製代碼

這樣就減小了你要寫的重複代碼,另外你還能夠把全部的 action 寫在一個對象裏面傳遞給 bindActionCreators,就像傳遞給 combineReducers 的對象那樣。

下面看看源碼:

/** * 該函數返回一個新的函數,調用新的函數會直接 dispatch ActionCreator 所返回的 action。 * 這個函數是 bindActionCreators 函數的基礎,在 bindActionCreators 函數中會把 actionCreators 拆分紅一個一個 * 的 ActionCreator,而後調用 bindActionCreator 方法。 * @param {Function} actionCreator 一個返回 action 純對象的函數。 * @param {Function} dispatch store.dispatch 方法,用於觸發 reducer。 */
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

//&emsp;接受一個 actionCreator(或者一個&emsp;actionCreators 對象)和一個&emsp;dispatch&emsp;函數做爲參數,
// 而後返回一個函數或者一個對象,直接執行這個函數或對象中的函數可讓你沒必要再調用&emsp;dispatch。
export default function bindActionCreators(actionCreators, dispatch) {
  // 若是 actionCreators 是一個函數而非對象,那麼直接調用 bindActionCreators 方法進行轉換,此時返回
  // 結果也是一個函數,執行這個函數會直接 dispatch 對應的&emsp;action。
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  //&emsp;actionCreators&emsp;既不是函數也不是對象,或者爲空時,拋出錯誤。
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${ actionCreators === null ? 'null' : typeof actionCreators }. ` +
        `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  //&emsp;若是&emsp;actionCreators 是一個對象,那麼它的每個屬性就應該是一個&emsp;actionCreator,遍歷每個&emsp;actionCreator,
  //&emsp;使用 bindActionCreator 進行轉換。
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    //&emsp;把轉換結果綁定到&emsp;boundActionCreators 對象,最後會返回它。
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
複製代碼

這部分挺簡單的,主要做用在於把 action creator 轉化爲能夠直接使用的函數。

3、中間件

看了源碼之後,以爲中間件並無想象中的那麼晦澀難懂了。就是一個基本的格式,而後你在你的中間件裏面能夠隨心所欲,最後調用固定的方法,返回固定的內容就完事了。這就是爲何大多數 redux middleware 的源碼都很短小精悍的緣由。

看看 redux-thunk 的源碼:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
複製代碼

是否是很短很小?那麼到底幹了什麼讓它這麼受歡迎呢?

實際上 redux-thunk 能夠被認爲就是:

// 這就是典型的 middleware 格式。
({ dispatch, getState }) => next => action => {
  // next 就是 dispatch 方法。註釋所在的函數就是返回的新的 dispatch。
  // 先判斷一下 action 是否是一個函數。
  if (typeof action === 'function') {
    // 若是是函數,調用它,傳遞 dispatch,getState 和 多餘的參數做爲 aciton 的參數。
    return action(dispatch, getState, extraArgument);
  }
  // 若是 action 不是函數,直接 nextr調用 action,返回結果就完事兒了。
  return next(action);
};
複製代碼

怎麼樣,是否是很簡單?它乾的事就是判斷了一下 action 的類型,若是是函數就調用,不是函數就用 dispatch 來調用,很簡單。

可是它實現的功能很實用,容許咱們傳遞函數做爲 store.dispatch 的參數,這個函數的參數應該是固定的,必須符合上面代碼的要求,接受 dispatch、getState做爲參數,而後這個函數應該返回實際的 action。

咱們也能夠寫一個本身的中間件了:

({ dispatch, getState }) => next => action => {
  return action.then ? action.then(next) : next(action);
}
複製代碼

這個中間件容許咱們傳遞一個 Promise 對象做爲 action,而後會等 action 返回結果(一個真正的 action)以後,再進行 dispatch。

固然因爲 action.then() 返回的不是實際上的 action(一個純對象),因此這個中間件可能無法跟其餘中間件一塊兒使用,否則其餘中間件接受不到 action 會出問題。這只是個示例,用於說明中間件沒那麼複雜,可是咱們能夠利用中間件作不少事情。

若是想要了解更加複雜的 redux 中間件,能夠參考:

4、總結

  • Redux 精妙小巧,主要利用了閉包和觀察者模式,而後運用了職責鏈、適配器等模式構建了一個 store 王國。store 擁有本身的領土,要想獲取或改變 store 裏面的內容,必須經過 store 的各個函數來實現。
  • Redux 相比於 Vuex 而言,代碼量更小,定製化程度更低,這就致使易用性低於 Vuex,可是可定製性高於 Vuex。這也符合 Vue 和 React 的風格。
  • Redux 源碼比較好懂,讀懂源碼更易於掌握 Redux 的使用方法,不要被嚇倒。
  • Redux 中間件短小精悍,比較實用。若是從使用方法開始學中間件比較難懂的話,能夠嘗試從源碼學習中間件。

最後,時間緊迫,水平有限,不免存在紕漏或錯誤,請你們多多包涵、多多指教、共同進步。

歡迎來個人 GitHub 下載項目源碼;或者 Follow me

相關文章
相關標籤/搜索