React系列之Redux 源碼探索

Github: 探索Reactjavascript

Redux 源碼探索

閱讀redux源碼,可讓咱們在實踐中更好使用和擴展它,甚至封裝出相似dva這樣的數據流方案。java

文件結構

|-- src
    |-- applyMiddleware.js  將middleware串聯起來生成一個更強大的dispatch函數,就是中間件的本質做用
    |-- bindActionCreators.js  將action creators轉換成擁有同名keys的action對象
    |-- combineReducers.js  將多個reducer組合起來,每個reducer獨立管理本身對應的state
    |-- compose.js  函數式編程中的組合函數,在applyMiddleware中調用,將middleware從右向左依次調用,生成一個從左向右執行middleware的dispatch加強函數
    |-- createStore.js  核心功能,建立一個store,實現了4個核心函數,dispatch、subscribe、getState、replaceReducer
    |-- index.js 對外暴露api
    |-- utils 供其餘的函數調用的工具函數或變量
        |-- actionTypes.js 內置action type變量,用於初始化/重置 state
        |-- isPlainObject.js 用於校驗是不是純對象
        |-- warning.js 錯誤提示函數
複製代碼

入口文件

import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

// 僅用於判斷代碼是否被壓縮
function isCrushed() {}

// 若是非生產環境壓縮了代碼會提示用戶,使用壓縮後的redux代碼會下降性能
if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning('~~~')
}

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

複製代碼
注意:actionTypes被暴露出去,可是並不但願被外部使用
複製代碼

建立倉庫 - createStore()

createStore 函數:react

  • 輸入三個參數:reducerpreloadedStateenhancer
    • reducer 須要是一個純函數,相同的輸入,必定會返回相同的輸出
    • preloadedState 初始值
    • enhancer 中文意思爲加強器,意思就是這裏能夠引入第三方庫,來加強store功能,redux的惟一加強器是 applyMiddleware() ,用於引入中間件來加強dispatch函數
  • 返回了4個主要函數:
    • dispatch : 發起action,執行reducer函數,若是有中間件,dispatch發起的action會通過中間件的擴展,而後再執行reducer函數
    • subscribe : 用於添加對state的訂閱
    • getState : 獲取state
    • replaceReducer : 動態替換reducer
  • 5個內部變量:
    • currentReducer : 保存當前的reducer
    • currentState : 保存應用的全局store狀態
    • currentListeners : 保存當前的訂閱函數數組
    • nextListeners : 訂閱函數數組的快照,避免直接修改currentListeners
    • isDispatching : 用於標識是否正在觸發一個action

下面再來看一下 createStore 源碼實現:git

// src/createStore.js
function createStore(reducer, preloadedState, enhancer) {
  // 這段代碼用於判斷傳遞的參數是否符合規範
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    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.'
    )
  }
  // 判斷第二個參數爲function時,第三個參數又不存在時,把第二個參數做爲enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 若是 enhancer 函數存在,執行 enhancer 函數,而且再也不繼續執行下去
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
  // reducer是必選參數且必須是一個函數
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
  
  // 聲明內部使用變量
  // 存儲值
  let currentReducer = reducer
  // 設置默認state
  let currentState = preloadedState
  // 存放回調函數的訂閱數組
  let currentListeners = []
  // 下一次的訂閱數組
  let nextListeners = currentListeners
  // 用於防止在執行reducer函數時再次觸發dispatch函數
  let isDispatching = false

  /** * 淺拷貝一份currentListeners 爲下一階段監聽器提供快照 * 用於防止在觸發訂閱/取消訂閱回調函數時出現錯誤 */
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /** * 獲取state * @returns {any} 返回currentState */
  function getState() {
    // 正在執行dispatch函數時不能獲取,其實通常狀況下不會出現這種狀況
    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
  }

  /** * 用於訂閱state的更新 * * @param {Function} listener 訂閱函數 * @returns {Function} 返回能夠移除listener的函數 */
  function subscribe(listener) {
    // 傳入的listener必須是一個函數
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
    // 保證純函數不帶來反作用
    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
    
    // 只有第一次執行或者每次dispatch以後的第一次subscribe時會把 currentListeners 拷貝並覆蓋 nextListeners 中
    ensureCanMutateNextListeners()
    // nextListeners 新增listener,currentListeners並不會新增
    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
      // 取消訂閱後 把 currentListeners 拷貝並覆蓋 nextListeners 中
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      // 從nextListeners中刪除unsubscribe的listener
      nextListeners.splice(index, 1)
      // 清空當前的訂閱數組
      currentListeners = null
    }
  }

  /** * 用於執行reducer函數的函數 */
  function dispatch(action) {
    // 保證action是個純對象,即字面量對象或Object建立的對象
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }
    // action必須有type屬性存在
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }
    // 正在執行reducer函數時不容許再次觸發
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // 執行reducer
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 通知全部以前經過subscribe訂閱state更新的回調listener
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /** * 用於替換當前存儲的reducer函數 * 好比當你須要進行代碼拆分或者想要動態加載一些reducer、想要爲redux實現熱重載時 * * @param {Function} nextReducer 用於替換 store 中的 reducer * @returns {void} */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer

    // ActionTypes.REPLACE其實就是ActionTypes.INIT
    // 從新INIT依次是爲了獲取新的reducer中的默認參數
    dispatch({ type: ActionTypes.REPLACE })
  }

  // 用於觀察和響應的互通 私有屬性,暫時並未不作擴充
  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
      }
    }
  }

  // reducer要求對沒法識別的action返回state,就是由於須要經過ActionTypes.INIT獲取默認參數值並返回
  // 當initailState和reducer的參數默認值都存在的時候,reducer參數默認值將不起做用
  // 由於在createStore聲明變量時currState就已經被賦值了initialState
  // 同時這個initialState也是服務端渲染的初始狀態入口
  dispatch({ type: ActionTypes.INIT })

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

複製代碼

總結如下幾點注意事項:github

  1. enhancer
// 判斷第二個參數爲function時,第三個參數又不存在時,把第二個參數做爲enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 若是 enhancer 函數存在,執行 enhancer 函數,而且再也不繼續執行下去
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
複製代碼

首先enhancer在缺省條件下,若是preloadedState是個函數,則將其視爲enhancerenhancer自己是個引入中間件擴展功能的返回函數,enhancer(createStore)(reducer, preloadedState)其實是輸出一個加強了dispatch功能的store 2. nextListenerscurrentListeners currentListeners 爲當前的 listener, nextListeners 爲下次 dispatch 後才發佈的訂閱者集合編程

對listener作深拷貝的緣由是由於若是在listener中進行unsubscribe操做,好比有3個listener(下標0,1,2),在第2個listener執行時unsubscribe了本身,那麼第3個listener的下標就變成了1,可是for循環下一輪的下標是2,第3個listener就被跳過了,因此執行一次深拷貝,即便在listener過程當中unsubscribe了也是更改的nextListeners(nextListeners會去深拷貝currentListeners)。當前執行的currentListeners不會被修改,第3個listener就不會被跳過了。redux

  1. observable 這個方法是爲Rxjs準備的,若是不使用RxJS能夠忽略,在這裏略過。
  2. replaceReducer 用於動態替換整個store的reducer

組合reducer - combineReducers()

用來將若干個reducer合併成一個reducers 源碼大部分都是用來校驗數據、拋出錯誤 輔助方法說明:api

// 用於
function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionDescription =
    (actionType && `action "${String(actionType)}"`) || 'an action'
  // 即便沒有值應該返回null,而不要返回undefined
  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.`
  )
}

function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) {
  const reducerKeys = Object.keys(reducers)
  // 判斷actionType是來自redux內部init type仍是其餘的
  const argumentName =
    action && action.type === ActionTypes.INIT
      ? 'preloadedState argument passed to createStore'
      : 'previous state received by the reducer'

  // 監測是否沒有傳遞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.'
    )
  }
  // state必須是個純對象
  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('", "')}"`
    )
  }
  // 判斷combineReducer合併的reducer個數是否和finalReducers個數是否相同
  // 並獲取不相同的key
  const unexpectedKeys = Object.keys(inputState).filter(
    key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  )
  // 標記不相同的key
  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })
  // 當是調用replaceReducers時,就不須要再判斷,直接返回
  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.`
    )
  }
}
// 用來判斷初始化和隨機狀態下返回的是否是 undefined
function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    // 遍歷 reducer
    const reducer = reducers[key]
    // 初始化 reducer,獲得一個state值
    const initialState = reducer(undefined, { type: ActionTypes.INIT })
    // 初始化狀態下 state 不能返回undefined
    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.`
      )
    }
    // 隨機狀態下 state 也能返回undefined
    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.`
      )
    }
  })
}

複製代碼

主要函數說明:數組

export default function combineReducers(reducers) {
  // 獲取傳入reducers的全部key
  const reducerKeys = Object.keys(reducers)
  // 最後真正有效的reducer存在這裏
  const finalReducers = {}
  // 篩選出有效的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}"`)
      }
    }
    // reducer必須是函數,無效的數據不會被合併進來
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  // 全部可用reducer
  const finalReducerKeys = Object.keys(finalReducers)

  // This is used to make sure we don't warn about the same
  // keys multiple times.
  // 用於配合getUnexpectedStateShapeWarningMessage輔助函數過濾掉多出來的值
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    // 校驗reducers是否都是有效數據
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  // 返回一個合併後的 reducer 函數,與普通的 reducer 同樣
  // 主要邏輯:取得每一個子reducer對應的state,與action一塊兒做爲參數給每一個子reducer執行。
  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 // 標誌state是否有變化
    const nextState = {}
    // 下面就會進行獲取reducer對應的state,與action一塊兒做爲參數給每一個子reducer執行。
    for (let i = 0; i < finalReducerKeys.length; i++) {
      // 獲取本次循環的子reducer
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 獲取該子reducer對應的舊的state
      const previousStateForKey = state[key]
      // 該子reducer執行後返回的新的state
      const nextStateForKey = reducer(previousStateForKey, action)
      // 若是新的state是undefined就拋出對應錯誤
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 用新的state替換舊的state
      nextState[key] = nextStateForKey
      // 判斷state是否改變
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 改變後返回新的state,未改變返回舊的值
    return hasChanged ? nextState : state
  }
}

複製代碼

添加中間件 - applyMiddleware()

applyMiddleware,顧名思義,就是運用中間件。 做用:把一系列中間件轉換爲加強dispatch的函數enhancer 源碼只有20行,主要內容只有十幾行,以下:bash

function applyMiddleware(...middlewares) {
  // middlewares 爲傳入的一系列中間件,等待調用
  // 這裏返回的就是enhancer,enhancer會接收createStore並返回一個函數
  // 在createStore中,當存在enhancer時,攔截了createStore下面須要執行的代碼,
  // 返回執行enhancer(createStore)(reducer, preloadedState)的結果
  return createStore => (...args) => {
    // 執行enhancer(createStore)(reducer, preloadedState)是會調用下面代碼
    // 繼續執行createStore,返回store
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
    // 傳遞給中間件使用的api
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 遍歷並執行中間件函數,把執行結果存放於chain
    // 每個中間件函數的返回值是一個改造的dispatch函數
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 用compose去改造dispatch函數,獲得一個加強的dispatch函數
    dispatch = compose(...chain)(store.dispatch)
    // compose(func1,func2,func3)
    // 返回一個函數: (...args) => func1( func2( func3(...args) ) )
    // 傳入的dispatch被func3改造後獲得一個新的dispatch,新的dispatch繼續被func2改造.
    // 所以每一箇中間件內部必須執行dispatch纔會繼續下一個中間件的調用

    // 繼續返回store,並用增強版的dispatch覆蓋舊的dispatch
    return {
      ...store,
      dispatch
    }
  }
}

複製代碼

綁定actionCreator - bindActionCreators

做用: 實際上就是經過返回一個高階函數,經過閉包應用,將dispatch隱藏起來,正常發起一個dispatch(action),可是bindActionCreators將dispatch隱藏,當執行bindActionCreators時:

  • 若是傳入的是一個函數,就認爲傳入了一個actionCreator,轉換成這樣:() => dispatch(actionCreators(...arguments))
  • 若是傳入的是對象,包含多個actionCreator,就會遍歷返回一個對象的對象,對每一個key值都進行上面的轉換
// 返回一個高階函數
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    // 執行actionCreator,獲取action,再用dispatch進行派發action
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
  // actionCreators若是是函數,就認爲就是一個actionCreator函數,
  // 直接調用bindActionCreator轉換生成一個能夠用dispatch派發的action
  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"?`
    )
  }
  // 這裏處理actionCreators是一組包含actionCreator的對象時
  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      // 每個key再次調用bindActionCreator進行轉換
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  // 返回處理後的對象
  return boundActionCreators
}
複製代碼

組合函數 - compose

函數式編程中經常使用的方法。

function compose(...funcs) {
  // 沒有參數的話直接返回一個函數組件
  // compose()(a) -> a
  // 先返回一個返回自身參數的函數 -> 函數執行,返回a
  if (funcs.length === 0) {
    return arg => arg
  }

  // 只有一個函數參數時返回該函數參數
  // compose(withA)(a) -> withA(a)
  // 先返回一個withA函數 -> 函數執行,而且參數爲a
  if (funcs.length === 1) {
    return funcs[0]
  }

  // 用reduce遍歷funcs,而且造成最終須要執行的函數
  // compose(withA, withB, withC, withD)(a) 
  // -> withA(withB(withC(withD(a))))
  return funcs.reduce((a, b) => {
    return (...args) => a(b(...args))
  })
  // 當a,b參數爲withA,withB時, return (...args) -> withA(withB(...args))
  // 當a,b參數爲上一輪返回函數,withC時, 
  // return (...args2) -> (...args) => withA(withB(...args))(withC(...args2)) 
  // 執行結果爲: 
  // (...args2) => withA(withB(withC))(withC(...args2))
  // ... 持續執行,最終結果返回一個函數,函數的參數放在funcs最後一個函數:
  // (...argsN) => withA(withB(withC(...withN(...argsN))))
}
複製代碼

參考連接

redux github倉庫

經過Github Blame深刻分析Redux源碼

一次性完全吸取 Redux 源碼

更好用的 Redux

JS函數式編程指南

相關文章
相關標籤/搜索