前端妹紙的進階之路——redux源碼分析

第一次看源碼,並無想象中的難哈,主要是redux的源碼比較少,理解起來也比較簡單。看過以後,感受更深刻的理解了redux思想和函數式編程的理念,建議你們能夠去看一下嘻嘻,看完以後確定會有收穫的。
javascript

我是對照着網上別人看過的源碼筆記看的,寫這篇文章的緣由呢,是想總結一下,由於我記性比較差啦,算是作一個筆記,方便之後複習。
java

redux的源碼中,有6個js文件,分別是:react

  • index.js
  • createStore.js
  • combineReducers.js
  • bindActionCreators.js
  • compose.js
  • applyMiddleware.js

咱們一個一個來分析吧~編程

index

這裏呢沒有太多須要講的,就是暴露了5個核心的api,分別是:redux

  • createStore:接收state和reducer,生成一顆狀態樹store
  • combineReducers:把子reducer合併成一個大reducer
  • bindActionCreators:把actionCreators和dispatch封裝成一個函數,也就是把他們兩綁定在一塊兒
  • applyMiddleware:這是一箇中間件
  • compose:一個組合函數

createStore

首先,定義初始化的actionapi

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

這個createStore函數,會傳入三個參數和一個返回值,分別是:
數組

一、 @param {Function} reducer
app

這個reducer是一個函數,這個函數接收state和action,做一系列計算以後返回一個新的state。這裏就體現了函數式編程的一些特性:
async

第一,這個reducer是一個純函數,純函數的特色是:對於相同的輸入,永遠會獲得相同的輸出,並且沒有任何可觀察的反作用,也不依賴外部環境的狀態。不理解純函數的筒子們,能夠上網搜索一下。
ide

第二,state是不可變的,咱們這裏對state做的一些修改和計算,不是直接修改原來的數據,而是返回修改以後的數據,原來的數據是保持不變。這裏能夠衍生到immutable,可使用immutable和redux搭配使用。

二、@param {any} [preloadedState]

這是初始化的state,很少說。

三、@param {Function} [enhancer]

這個enhancer其實就是一箇中間件,它在redux3.1.0以後才加入的。至關於把store作一些加強處理,讓store更強大,功能更豐富,在以後的applyMiddleware那裏會詳細說的。這裏也體現了高階函數的思想,就像react-redux的connect方法同樣,作一些包裝處理以後,再返回。

四、@returns {Store}

這是返回的值,返回的是一棵狀態樹,也就是store啦。

這是作的源碼分析,都寫在註釋裏了。createStore返回的最經常使用的三個api是dispatch,subscribe,getState,通常咱們只要傳入reducer和preloadedState,就能夠直接調用這三個方法,很是方便。

export default function createStore(reducer, preloadedState, enhancer) {
  //這裏是一些參數校驗
  //若是第二個參數爲函數且沒有傳入第三個參數,那就交換第二個參數和第三個參數
  //意思是createSotre會認爲你忽略了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.')
    }

    //這是一個高階函數調用方法。這裏的enhancer就是applyMiddleware(...middlewares)
    //enhancer接受createStore做爲參數,對createStore的能力進行加強,並返回加強後的createStore
    //而後再將reducer和preloadedState做爲參數傳給加強後的createStore,獲得最終生成的store
    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//當前的狀態樹
  let currentListeners = [] // 當前的監聽器列表
  let nextListeners = currentListeners //更新後的監聽器列表
  let isDispatching = false //是否正在dispatch

  //判斷當前listener和更新後的listener是否是同一個引用,若是是的話對當前listener進行一個拷貝,防止在操做新的listener列表的時候對正在發生的業務邏輯形成影響
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   *
   * @returns {any} The current state tree of your application.
   */
  //返回當前的狀態樹
  function getState() {
    return currentState
  }

  /**
   *這個函數是給store添加監聽函數,把listener做爲一個參數傳入,
   *註冊監聽這個函數以後,subscribe方法會返回一個unsubscribe()方法,來註銷剛纔添加的監聽函數
   * @param {Function} listener 傳入一個監聽器函數
   * @returns {Function} 
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }
    //註冊監聽
    let isSubscribed = true

    ensureCanMutateNextListeners()
    //將監聽器壓進nextListeners隊列中
    nextListeners.push(listener)

    //註冊監聽以後,要返回一個取消監聽的函數
    return function unsubscribe() {
      //若是已經取消監聽了,就返回
      if (!isSubscribed) {
        return
      }
      //取消監聽
      isSubscribed = false

      //在nextListeners中找到這個監聽器,而且刪除
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /**
   * @param {Object} action 傳入一個action對象
   *
   * @returns {Object} 
   */
  function dispatch(action) {
    //校驗action是否爲一個原生js對象
    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?'
      )
    }

    //判斷是否正在派發,主要是避免派發死循環
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    //設置正在派發的標誌位,而後將當前的state和action傳給當前的reducer,用於生成新的state
    //這就是reducer的工做過程,純函數接受state和action,再返回一個新的state
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    //獲得新的state以後,遍歷當前的監聽列表,依次調用全部的監聽函數,通知狀態的變動
    //這裏沒有把最新的狀態做爲參數傳給監聽函數,是由於能夠直接調用store.getState()方法拿到最新的狀態
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    //返回action
    return action
  }

  /**
   *這個方法主要用於reducer的熱替換
   * @param {Function} nextReducer 
   * @returns {void}
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }
    // 把傳入的nextReducer給當前的reducer
    currentReducer = nextReducer
    //dispatch一個初始action
    dispatch({ type: ActionTypes.INIT })
  }

  /**
   * 用於提供觀察者模式的操做,貌似是一個預留的方法,暫時沒看到有啥用
   * @returns {observable} A minimal observable of state changes.
   */
  function observable() {
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer 
       * 觀察者應該有next方法
       * @returns {subscription} 
       */
      subscribe(observer) {
        //觀察者模式的鏈式結構,傳入當前的state
        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
      }
    }
  }
  //初始化一個action
  dispatch({ type: ActionTypes.INIT })

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

combineReducers

combineReducers的做用是將以前切分的多個子reducer合併成一個大的reducer,也就是說將不少的小狀態樹合併到一棵樹上,整合成一棵完整的狀態樹。

這個函數接受一個參數,返回一個函數

一、@param {Object} reducers

這裏接收多個reducer,傳入的是一個對象

二、@returns {Function}

combineReducers的整個執行過程就是:將全部符合標準的reducer放進一個對象中,當dispatch一個action的時候,就遍歷每一個reducer,來計算出每一個reducer的state值。同時,每遍歷一個reducer,就判斷新舊state是否發生改變,來決定是返回新state仍是舊state,這是作的一個優化處理。

源碼分析以下,前面還有一部分是一些error信息和warning信息的處理,就沒有放進來了,感興趣的話能夠本身去看一下完整的源碼。

export default function combineReducers(reducers) {
  //獲取reducers的全部key值
  const reducerKeys = Object.keys(reducers)
  //最終生成的reducer對象
  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}"`)
      }
    }
    //遍歷reducer,把key值都是function的reducer放進finalReducers對象中
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  //獲得finalReducers的key值數組
  const finalReducerKeys = Object.keys(finalReducers)

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

  //檢測這些reducer是否符合標準
  let shapeAssertionError
  try {
    //檢測是不是redux規定的reducer形式
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  //計算state的邏輯部分
  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    //若是不是production(線上)環境,作一些警告
    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    //標誌state是否改變
    let hasChanged = false
    //存儲新的state
    const nextState = {}

    for (let i = 0; i < finalReducerKeys.length; i++) {
      //遍歷finalReducerKeys的key值,也就是reducer的名字
      const key = finalReducerKeys[i]
      //獲得reducer的vlaue值
      const reducer = finalReducers[key]
      //變化前的state值
      const previousStateForKey = state[key]
      //變化後的state值,把變化前的state和action傳進去,計算出新的state
      const nextStateForKey = reducer(previousStateForKey, action)
      //若是沒有返回新的reducer,就拋出異常
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      //把變化後的state存入nextState數組中
      nextState[key] = nextStateForKey
      //判斷state是否有改變
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    //若是改變了state就返回新的state,沒改變就返回原來的state
    return hasChanged ? nextState : state
  }
}

bindActionCreators

bindActionCreators的做用是:將action與dispatch函數綁定,生成能夠直接觸發action的函數。

//使用dispatch包裝actionCreator方法
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}
/*
 * @param {Function|Object} actionCreators
 *
 * @param {Function} dispatch 
 *
 * @returns {Function|Object}
 * 
 */
export default function bindActionCreators(actionCreators, dispatch) {
  //actionCreators爲函數,就直接調用bindActionCreator進行包裝
  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爲對象時的操做
  //遍歷actionCreators對象的key值
  const keys = Object.keys(actionCreators)
  //存儲dispatch和actionCreator綁定以後的集合
  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)
    }
  }
  return boundActionCreators
}

compose

compose叫作函數組合,是一個柯里化函數,將多個函數合併成一個函數,從右到左執行。這同時也是函數式編程的特性。這個函數會在applyMiddleware中用到

/**
 * @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不是以前的reducer,這裏的reduce函數是es5的一個歸併數組的方法,是從數組的第一項開始,逐個遍歷數組的全部項。

它接收兩個參數,一個是在每一項上調用的函數,還有一個可選參數,是做爲歸併基礎的初始值。調用的那個函數又接收四個參數,前一個值,當前值,項的索引,和數組對象。這個函數返回的任何值都會做爲第一個參數自動傳遞給下一項。這樣說可能比較抽象,舉個例子:

[1,2,3,4,5].reduce((prev, cur) => {
    return prev + cur //輸出15
})

用reduce就能夠很快的求的數組全部值相加的和。另外,還有一個reduceRight()方法,跟reduce是同樣的,只不過是從數組的右邊開始遍歷的。

咱們回到源碼上面return funcs.reduce((a, b) => (...args) => a(b(...args))),這其實就是遍歷傳入的參數數組(函數),將這些函數合併成一個函數,從右到左的執行。這就是中間件的創造過程,把store用一個函數包裝以後,又用另外一個函數包裝,就造成了這種包菜式的函數。

applyMiddleware

applyMiddleware是用來擴展redux功能的,主要就是擴展store.dispatch的功能,像logger、redux-thunk就是一些中間件。

它的實現過程是:在dispatch的時候,會按照在applyMiddleware時傳入的中間件順序,依次執行。最後返回一個通過許多中間件包裝以後的store.dispatch方法。

若是理解了以前說的compose函數,這一段代碼應該也很容易就能看懂啦。

/**
 * @param {...Function} middlewares 接收不定數量的中間件函數
 * @returns {Function} 返回一個通過中間件包裝以後的store
 */
export default function applyMiddleware(...middlewares) {
  //返回一個參數爲createStore的匿名函數
  return (createStore) => (reducer, preloadedState, enhancer) => {
    //生成store
    const store = createStore(reducer, preloadedState, enhancer)
    //獲得dispatch方法
    let dispatch = store.dispatch
    //定義中間件的chain
    let chain = []

    //在中間件中要用到的兩個方法
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    //把這兩個api給中間件包裝一次
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    //鏈式調用每個中間件,給dispatch進行封裝,再返回最後包裝以後的dispatch
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

總結

整個的源碼就所有分析完了,咱們能夠看到,redux的源碼不少地方都體現了函數式編程的思想。函數式編程寫出來的代碼確實很漂亮很簡潔,可是理解起來也比較困難。這也只是函數式編程的很小一部分,有興趣的話能夠去了解一下其餘的部分。

寫到這裏也差很少了,但願之後有機會能多看點源碼,get一些新的知識,最後感謝宋老師的寶貴意見,bye

相關文章
相關標籤/搜索