解密Redux: 從源碼開始

Redux是當今比較流行的狀態管理庫,它不依賴於任何的框架,而且配合着react-redux的使用,Redux在不少公司的React項目中起到了舉足輕重的做用。接下來筆者就從源碼中探尋Redux是如何實現的。javascript

注意:本文不去過多的講解Redux的使用方法,更多的使用方法和最佳實踐請移步 Redux官網

源碼以前

基礎概念

隨着咱們項目的複雜,項目中的狀態就變得難以維護起來,這些狀態在何時,處於什麼緣由,怎樣變化的咱們就很難去控制。所以咱們考慮在項目中引入諸如Redux、Mobx這樣的狀態管理工具。java

Redux其實很簡單,能夠簡單理解爲一個約束了特定規則而且包括了一些特殊概念的的發佈訂閱器。react

在Redux中,咱們用一個store來管理一個一個的state。當咱們想要去修改一個state的時候,咱們須要去發起一個action,這個action告訴Redux發生了哪一個動做,可是action不可以去直接修改store裏頭的state,他須要藉助reducer來描述這個行爲,reducer接受state和action,來返回新的state。redux

三大原則

在Redux中有三大原則:數組

  • 單一數據源:全部的state都存儲在一個對象中,而且這個對象只存在於惟一的store中;
  • state只讀性:惟一改變state的方法就是去觸發一個action,action用來描述發生了哪一個行爲;
  • 使用純函數來執行修改:reducer描述了action如何去修改state,reducer必須是一個純函數,一樣的輸入必須有一樣的輸出;

剖析源碼

項目結構

項目結構

拋去一些項目的配置文件和其餘,Redux的源碼其實不多很簡單:閉包

  • index.js:入口文件,導出另外幾個核心函數;
  • createStore.js:store相關的核心代碼邏輯,本質是一個發佈訂閱器;
  • combineReducers.js:用來合併多個reducer到一個root reducer的相關邏輯;
  • bindActionCreators.js:用來自動dispatch的一個方法;
  • applyMiddleware.js:用來處理使用的中間件;
  • compose.js:導出一個經過從右到左組合參數函數得到的函數;
  • utils:兩個個工具函數和一個系統註冊的actionType;

從createStore來說一個store的建立

首先咱們先經過createStore函數的入參和返回值來簡要理解它的功能:app

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

  // ...

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

createStore接受三個參數:框架

  • reducer:用來描述action如何改變state的方法,它給定當前state和要處理的action,返回下一個state;
  • preloadedState:顧名思義就是初始化的state;
  • enhancer:能夠直譯爲加強器,用它來加強store的第三方功能,Redux附帶的惟一store加強器是applyMiddleware

createStore返回一個對象,對象中包含使用store的基本函數:異步

  • dispatch:用於action的分發;
  • subscribe:訂閱器,他將會在每次action被dispatch的時候調用;
  • getState:獲取store中的state值;
  • replaceReducer:替換reducer的相關邏輯;

接下來咱們來看看createStore的核心邏輯,這裏我省略了一些簡單的警告和判斷邏輯:函數

export default function createStore(reducer, preloadedState, enhancer) {
  // 判斷是否是傳入了過多的enhancer
  // ...

  // 若是不傳入preloadedState只傳入enhancer能夠寫成,const store = createStore(reducers, enhancer)
  // ...

  // 經過在加強器傳入createStore來加強store的基本功能,其餘傳入的參數做爲返回的高階函數參數傳入;
  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.')
  }

  // 閉包內的變量;
  // state做爲內部變量不對外暴露,保持「只讀」性,僅經過reducer去修改
  let currentReducer = reducer
  let currentState = preloadedState
  // 確保咱們所操做的listener列表不是原始的listener列表,僅是他的一個副本;
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 確保咱們所操做的listener列表不是原始的listener列表,僅是他的一個副本;
  // 只有在dispatch的時候,纔會去將currentListeners和nextListeners更新成一個;
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 經過閉包返回了state,state僅能夠經過此方法訪問;
  function getState() {
    // 判斷當前是否在dispatch過程當中
    // ...

    return currentState
  }

  // Redux內部的發佈訂閱器
  function subscribe(listener) {
    // 判斷listener的合法性
    // ...

    // 判斷當前是否在dispatch過程當中
    // ...

    let isSubscribed = true

    // 複製一份當前的listener副本
    // 操做的都是副本而不是源數據
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

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

      // 判斷當前是否在dispatch過程當中
      // ...

      isSubscribed = false

      ensureCanMutateNextListeners()

      // 根據當前listener的索引從listener數組中刪除來實現取掉訂閱;
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  function dispatch(action) {
    // 判斷action是否是一個普通對象;
    // ...

    // 判斷action的type是否合法
    // ...

    // 判斷當前是否在dispatch過程當中
    // ...

    try {
      isDispatching = true
      // 根據要觸發的action, 經過reducer來更新當前的state;
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 通知listener執行對應的操做;
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 替換reducer,修改state變化的邏輯
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer

    // 此操做對ActionTypes.INIT具備相似的效果。
    // 新舊rootReducer中存在的任何reducer都將收到先前的狀態。
    // 這有效地使用來自舊狀態樹的任何相關數據填充新狀態樹。
    dispatch({ type: ActionTypes.REPLACE })
  }

  function observable() {
    const outerSubscribe = subscribe
    return {
      // 任何對象均可以被用做observer,observer對象應該有一個next方法
      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)
        // 返回一個帶有unsubscribe方法的對象能夠被用來在store中取消訂閱
        return { unsubscribe }
      },

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

  // 建立store時,將調度「INIT」操做,以便每一個reducer返回其初始狀態,以便state的初始化。
  dispatch({ type: ActionTypes.INIT })

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

從combineReducers談store的惟一性

僅靠上面的createStore其實已經能夠完成一個簡單的狀態管理了,可是隨着業務體量的增大,state、action、reducer也會隨之增大,咱們不可能把全部的東西都塞到一個reducer裏,最好是劃分紅不一樣的reducer來處理不一樣模塊的業務。

可是也不能建立多個store維護各自的reducer,這就違背了Redux的單一store原則。爲此,Redux提供了combineReducers讓咱們將按照業務模塊劃分的reducer合成一個rootReducer。

接下來咱們看看combineReducers的源碼,這裏也是去掉了一些錯誤警告的代碼和一些錯誤處理方法:

export default function combineReducers(reducers) {
  // 取出全部的reducer遍歷合併到一個對象中
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    // 判斷未匹配的refucer
    // ...

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

   // 錯誤處理的一些邏輯
   // ...

  return function combination(state = {}, action) {

    // 錯誤處理的一些邏輯
    // ...

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      // 對應的reducer
      const reducer = finalReducers[key]
      // 根據指定的reducer找到對應的state
      const previousStateForKey = state[key]
      // 執行reducer, 返回當前state
      const nextStateForKey = reducer(previousStateForKey, action)
      // nextStateForKey undefined的一些判斷
      // ...

      // 整合每個reducer對應的state
      nextState[key] = nextStateForKey
      // 判斷新的state是否是同一引用, 以檢驗reducer是否是純函數
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

其實到這裏能夠簡單的看出combineReducers就是把多個reducer拉伸展開到到一個對象裏,一樣也把每個reducer裏的state拉伸到一個對象裏。

從bindActionCreators談如何自動dispatch

現有的store每一次state的更新都須要手動的dispatch每個action,而咱們其實更須要的是自動的dispatch全部的action。這裏就用到了bindActionCreators方法。

如今咱們來看看bindActionCreators的源碼

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 返回綁定了this的actionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  // actionCreators類型判斷的錯誤處理
  // ...

  // 爲每個actionCreator綁定this
  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

其實咱們在react項目中對這個方法是幾乎無感知的,由於是在react-redux的connect中調用了這個方法來實現自動dispatch action的,否則須要手動去dispatch一個個action。

從compose談函數組合

compose是Redux導出的一個方法,這方法就是利用了函數式的思想對函數進行組合:

// 經過從右到左組合參數函數得到的函數。例如,compose(f, g, h)與do(...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)))
}

從applyMiddleware談如何自定義dispatch

咱們的action會出現同步的場景,固然也會出現異步的場景,在這兩種場景下dispacth的執行時機是不一樣的,在Redux中,可使用middleware來對dispatch進行改造,下面咱們來看看applyMiddleware的實現:

import compose from './compose'

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    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.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 經過從右到左組合參數函數得到的函數。例如,compose(f, g, h)與do(...args)=> f(g(h(... args)))相同。
    // 對dispatch改造
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

結語

到此,Redux源碼的部分就分析完了,可是在具體和React結合的時候還須要用到react-redux,下一篇文章,我將深刻到react-redux的源碼學習,來探索,在react中,咱們如何去使用Redux。

相關文章
相關標籤/搜索