Redux: 百行代碼千行文檔

pbmz5292 7年前發布 | 27K 次閱讀 Redux

接觸Redux不過短短半年,從開始看官方文檔的一頭霧水,到漸漸已經理解了Redux到底是在做什么,但是絕大數場景下Redux都是配合React一同使用的,因而會引入了React-Redux庫,但是正是因為React-Redux庫封裝了大量方法,使得我們對Redux的理解變的開始模糊。這篇文章將會在Redux源碼的角度分析Redux,希望你在閱讀之前有部分Redux的基礎。

上圖是Redux的流程圖,具體的不做介紹,不了解的同學可以查閱一下Redux的官方文檔。寫的非常詳細。下面的代碼結構為Redux的master分支:

├── applyMiddleware.js

├── bindActionCreators.js

├── combineReducers.js

├── compose.js

├── createStore.js

├── index.js

└── utils

└── warning.js

Redux中src文件夾下目錄如上所示,文件名基本就是對應我們所熟悉的Redux的API,首先看一下index.js中的代碼:

/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
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 DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
    'to ensure you have the correct code for your production build.'
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

上面的代碼非常的簡單了,只不過是把所有的方法對外導出。其中 isCrushed 是用來檢查函數名是否已經被壓縮(minification)。如果函數當前不是在生產環境中并且函數名被壓縮了,就提示用戶。process是Node 應用自帶的一個全局變量,可以獲取當前進程的若干信息。在許多前端庫中,經常會使用 process.env.NODE_ENV這個環境變量來判斷當前是在開發環境還是生產環境中。這個小例子我們可以get到一個hack的方法,如果判斷一個js函數名時候被壓縮呢?我們可以先預定義一個虛函數(雖然JavaScript中沒有虛函數一說,這里的虛函數(dummy function)指代的是沒有函數體的函數),然后判斷執行時的函數名是否和預定義的一樣,就像上面的代碼:

function isCrushed() {}
if(typeof isCrushed.name === 'string' && isCrushed.name === 'isCrushed'){
    //has minified
}

compose

從易到難,我們在看一個稍微簡單的對外方法 compose

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

理解這個函數之前我們首先看一下 reduce 方法,這個方法我是看了好多遍現在仍然是印象模糊,雖然之前介紹過 reduce ,但是還是再次回憶一下 Array.prototye.reduce :

The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.

reduce() 函數對一個累加值和數組中的每一個元素(從左到右)應用一個函數,將其 reduce 成一個單值,例如:

var sum = [0, 1, 2, 3].reduce(function(acc, val) {
  return acc + val;
}, 0);
// sum is 6

reduce() 函數接受兩個參數:一個回調函數和初始值,回調函數會被從左到右應用到數組的每一個元素,其中回調函數的定義是

/**
 * accumulator: 累加器累加回調的值,它是上一次調用回調時返回的累積值或者是初始值
 * currentValue: 當前數組遍歷的值
 * currenIndex: 當前元素的索引值
 * array: 整個數組
 */
function (accumulator,currentValue,currentIndex,array){

}

現在回頭看看 compose 函數都在做什么, compose 函數從左到右組合(compose)多個單參函數。最右邊的函數可以按照定義接受多個參數,如果 compose 的參數為空,則返回一個空函數。如果參數長度為1,則返回函數本身。如果函數的參數為數組,這時候我們返回

return funcs.reduce((a, b) => (...args) => a(b(...args)))

我們知道 reduce 函數返回是一個值。上面函數傳入的回調函數是 (a, b) => (...args) => a(b(...args)) 其中 a 是當前的累積值, b 是數組中當前遍歷的值。假設調用函數的方式是 compose(f,g,h) ,首先第一次執行回調函數時, a 的實參是函數 f , b 的實參是 g ,第二次調用的是, a 的實參是 (...args) => f(g(...args)) , b 的實參是 h ,最后函數返回的是 (...args) =>x(h(...args)) ,其中x為 (...args) => f(g(...args)) ,所以我們最后可以推導出運行 compose(f,g,h) 的結果是 (...args) => f(g(h(...args))) 。發現了沒有,這里其實通過 reduce 實現了 reduceRight 的從右到左遍歷的功能,但是卻使得代碼相對較難理解。在Redux 1.0.1版本中 compose 的實現如下:

export default function compose(...funcs) {
     return funcs.reduceRight((composed, f) => f(composed));
}

這樣看起來是不是更容易理解 compose 函數的功能。

bindActionCreators

bindActionCreators 也是Redux中非常常見的API,主要實現的就是將 ActionCreator 與 dispatch 進行綁定,看一下官方的解釋:

Turns an object whose values are action creators, into an object with the same keys, but with every action creator wrapped into a dispatch call so they may be invoked directly.

翻譯過來就是 bindActionCreators 將值為 actionCreator 的對象轉化成具有相同鍵值的對象,但是每一個 actionCreator 都會被 dispatch 所包裹調用,因此可以直接使用。話不多說,來看看它是怎么實現的:

import warning from './utils/warning'

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  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"?`
    )
  }

  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    } else {
      warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)
    }
  }
  return boundActionCreators
}

對于處理單個 actionCreator 的方法是

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

代碼也是非常的簡單,無非是返回一個新的函數,該函數調用時會將 actionCreator 返回的純對象進行 dispatch 。而對于函數 bindActionCreators 首先會判斷 actionCreators 是不是函數,如果是函數就直接調用 bindActionCreator 。當 actionCreators 不是對象時會拋出錯誤。接下來:

const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    } else {
      warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)
    }
  }
  return boundActionCreators

這段代碼也是非常簡單,甚至我覺得我都能寫出來,無非就是對對象 actionCreators 中的所有值調用 bindActionCreator ,然后返回新的對象。恭喜你,又解鎖了一個文件~

applyMiddleware

applyMiddleware 是Redux Middleware的一個重要API,這個部分代碼已經不需要再次解釋了,沒有看過的同學戳這里 Redux:Middleware你咋就這么難 ,里面有詳細的介紹。

createStore

createStore 作為Redux的核心API,其作用就是生成一個應用唯一的store。其函數的簽名為:

function createStore(reducer, preloadedState, enhancer) {}

前兩個參數非常熟悉, reducer 是處理的 reducer 純函數, preloadedState 是初始狀態,而 enhancer 使用相對較少, enhancer 是一個高階函數,用來對原始的 createStore 的功能進行增強。具體我們可以看一下源碼:

具體代碼如下:

import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

export const ActionTypes = {
  INIT: '@@redux/INIT'
}

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') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

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

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

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


  function getState() {
    return currentState
  }

  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

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

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeof observer !== 'object') {
          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
      }
    }
  }

  dispatch({ type: ActionTypes.INIT })

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

我們來逐步解讀一下:

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

我們發現如果沒有傳入參數 enhancer ,并且 preloadedState 的值又是一個函數的話, createStore 會認為你省略了 preloadedState ,因此第二個參數就是 enhancer 。

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

    return enhancer(createStore)(reducer, preloadedState)
  }

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

如果你傳入了 enhancer 但是卻又不是函數類型。會拋出錯誤。如果傳入的 reducer 也不是函數,拋出相關錯誤。接下來才是 createStore 重點,初始化:

let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

currentReducer 是用來存儲當前的 reducer 函數。 currentState 用來存儲當前store中的數據,初始化為默認的 preloadedState , currentListeners 用來存儲當前的監聽者。而 isDispatching 用來當前是否屬于正在處理 dispatch 的階段。然后函數聲明了一系列函數,最后返回了:

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

顯然可以看出來返回來的函數就是 store 。比如我們可以調用 store.dispatch 。讓我們依次看看各個函數在做什么。

dispatch

function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = currentListeners = nextListeners

    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

我們看看 dispath 做了什么,首先檢查傳入的 action 是不是純對象,如果不是則拋出異常。然后檢測, action 中是否存在 type ,不存在也給出相應的錯誤提示。然后判斷 isDispatching 是否為 true ,主要是預防的是在 reducer 中做 dispatch 操作,如果在 reduder 中做了 dispatch ,而 dispatch 又必然會導致 reducer 的調用,就會造成死循環。然后我們將 isDispatching 置為 true ,調用當前的 reducer 函數,并且返回新的 state 存入 currentState ,并將 isDispatching 置回去。最后依次調用監聽者 store 已經發生了變化,但是我們并沒有將新的 store 作為參數傳遞給監聽者,因為我們知道監聽者函數內部可以通過調用唯一獲取 store 的函數 store.getState() 獲取最新的 store 。

getState

function getState() {
    return currentState
  }

實在太簡單了,自行體會。

replaceReducer

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

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

replaceReducer 的使用相對也是非常少的,主要用戶熱更新 reducer 。

subscribe

function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

subscribe 用來訂閱 store 變化的函數。首先判斷傳入的 listener 是否是函數。然后又調用了 ensureCanMutateNextListeners ,

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

可以看到 ensureCanMutateNextListeners 用來判斷 nextListeners 和 currentListeners 是否是完全相同,如果不相同,將 nextListeners 賦值為 nextListeners (值相同,但不是同一個數組),然后將當前的監聽函數傳入 nextListeners 。最后返回一個 unsubscribe 函數用來移除當前監聽者函數。需要注意的是, isSubscribed 是以閉包的形式判斷當前監聽者函數是否在監聽,從而保證只有第一次調用 unsubscribe 才是有效的。但是為什么會存在 nextListeners 呢?

首先可以在任何時間點添加 listener 。無論是 dispatch action時,還是 state 值正在發生改變的時候。但是需要注意的,在每一次調用 dispatch 之前,訂閱者僅僅只是一份快照(snapshot),如果是在 listeners 被調用期間發生訂閱(subscribe)或者解除訂閱(unsubscribe),在本次通知中并不會立即生效,而是在下次中生效。因此添加的過程是在 nextListeners 中添加的訂閱者,而不是直接添加到 currentListeners 。然后在每一次調用 dispatch 的時候都會做:

const listeners = currentListeners = nextListeners

來同步 currentListeners 和 nextListeners 。

observable

該部分不屬于本次文章講解到的內容,主要涉及到RxJS和響應異步Action。以后有機會(主要是我自己搞明白了),會單獨講解。

combineReducers

combineReducers 的主要作用就是將大的 reducer 函數拆分成一個個小的 reducer 分別處理,看一下它是如何實現的:

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  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') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

首先,通過一個 for 循環去遍歷參數 reducers ,將對應值為函數的屬性賦值到 finalReducers 。然后聲明變量 unexpectedKeyCache ,如果在非生產環境,會將其初始化為 {} 。然后執行 assertReducerShape(finalReducers) ,如果拋出異常會將錯誤信息存儲在 shapeAssertionError 。我們看一下 shapeAssertionError 在做什么?

function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    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.`
      )
    }

    const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    if (typeof reducer(undefined, { type }) === '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.`
      )
    }
  })
}

可以看出 assertReducerShape 函數的主要作用就是判斷 reducers 中的每一個 reducer 在 action 為 { type: ActionTypes.INIT } 時是否有初始值,如果沒有則會拋出異常。并且會對 reduer 執行一次隨機的 action ,如果沒有返回,則拋出錯誤,告知你不要處理redux中的私有的action,對于未知的action應當返回當前的stat。并且初始值不能為 undefined 但是可以是 null 。

接著我們看到 combineReducers 返回了一個 combineReducers 函數:

return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
}

在 combination 函數中我們首先對 shapeAssertionError 中可能存在的異常進行處理。接著,如果是在開發環境下,會執行 getUnexpectedStateShapeWarningMessage ,看看 getUnexpectedStateShapeWarningMessage 是如何定義的:

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'

  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] +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }

  const unexpectedKeys = Object.keys(inputState).filter(key =>
    !reducers.hasOwnProperty(key) &&
    !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })

  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.`
    )
  }
}

我們簡要地看看 getUnexpectedStateShapeWarningMessage 處理了哪幾種問題:

  1. reducer中是不是存在reducer
  2. state是否是純Object對象
  3. state中存在reducer沒有處理的項,但是僅會在第一次提醒,之后就忽略了。

然后 combination 執行其核心部分代碼:

let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state

使用變量 nextState 記錄本次執行 reducer 返回的state。 hasChanged 用來記錄前后 state 是否發生改變。循環遍歷 reducers ,將對應的 store 的部分交給相關的 reducer 處理,當然對應各個 reducer 返回的新的 state 仍然不可以是 undefined 。最后根據 hasChanged 是否改變來決定返回 nextState 還是 state ,這樣就保證了在不變的情況下仍然返回的是同一個對象。

最后,其實我們發現Redux的源碼非常的精煉,也并不復雜,但是Dan Abramov能從Flux的思想演變到現在的Redux思想也是非常不易,希望此篇文章使得你對Redux有更深的理解。

 

來自:https://juejin.im/post/5930a1bdfe88c2006198bdf0

 

 本文由用戶 pbmz5292 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!