閱讀redux源碼

redux源碼解析

什麼是redux

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。html

爲何須要使用redux

提供了和雙向綁定思想不一樣的單向數據流,應用狀態能夠預測,能夠回溯,易於調試。使用redux之初的人可能會很不適應,改變一個狀態,至少寫三個方法,從這點上不如寫其餘框架代碼易於理解,可是自從配合使用redux-logger一類的logger插件,就感受到了redux的優點。狀態改變很清晰,很容易瞭解發生了什麼。react

源碼解析

注意: 若是沒有使用過redux,建議先去看看redux文檔express

api方法

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

能夠看到咱們在react代碼中使用到的api,通常主動調用的就是 combineReducers ,其餘部分參照例子基本能夠搬過來redux

combineReducers

打開combineReducers.js,先看export的方法,也就是combineReducers方法api

var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var 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]
    }
  }

首先看到這個函數接收的是一個對象,而這個這個對象的內部數據值必須是一個函數,否則會警告。循環了一遍這個對象,獲得一個新值,對象值所有是函數的一個新reducers數組

var finalReducerKeys = Object.keys(finalReducers)

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

  var sanityError
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

這裏好像還在判斷這個最後reducers的合法性,那這裏是在判斷什麼呢?咱們來看看 assertReducerSanity 方法app

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

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

這塊其實就兩個判斷,reducer被執行了兩次,一個是判斷沒有初始化state的,reducer的返回值,一個判斷action沒有type的時候的返回值。一個沒有返回值都會有警告,因此咱們寫reducer的時候都會指定一個默認返回值。框架

reducer會被執行屢次,這也是咱們爲何要保證reducer的純粹性,不能作任何其餘的操做的緣由less

繼續往下看 combineReducersdom

能夠看到返回了一個函數 combination(state = {}, action) 。爲何返回函數呢?

那咱們看 combination(state = {}, action) 像什麼?不就是咱們常常寫的reducer嘛!這個reducer最終會被store傳入初始state而且看成純函數調用,而reducer裏面是能夠嵌套combineReducers的結果的,因此咱們在使用狀態的時候,常常會這樣 state.user.login 這樣子的相似狀態調用

這塊想明白仍是有點複雜,全部的reducer都是一個相同的函數combination,接收state參數,內部執行一樣是combination,直到沒有combineReducers爲止,纔開始執行咱們本身寫的reducer函數,獲得的值使用combineReducers參數的對象的key做爲state的key,咱們本身寫的reducers執行結果獲得的值做爲state的value。最終獲得的就是一個巨大的Object,這就是咱們的store中的state。

createStore

通常這個方法咱們能夠直接從demo中複製過來,不須要太過了解,可是既然要深刻了解redux,必然要掌握這個方法

跟以前同樣,先找到 export createStore 方法,能夠看到這個函數接受三個參數

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

第一個reducer: 上文講到的combineReducer返回的reducer函數

第二個preloadedState:redux初始化state,能夠不傳

第三個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.')
  }

能夠看到第一個判斷的意思是當沒有第二個參數是函數的時候,默認第二個參數就是中間件,而且默認state置爲undefined

第二個判斷的意思是當有中間件參數,可是中間參數類型不是function的時候,拋出一個非法錯誤,若是是函數,先執行中間件,退出。後續在講中間件是怎麼執行的

第三個判斷reducer是不是函數,不然拋出錯誤退出

var currentReducer = reducer         // 當前reducer
  var currentState = preloadedState    // 當前state
  var currentListeners = []            // 當前監聽器
  var nextListeners = currentListeners // 下一個監聽器
  var isDispatching = false            // 重複dispatch的狀態標記

再看看createStore的返回值

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

這不是store的方法嘛,挨個看看

function getState() {
    return currentState
  }

這個沒什麼好說的。

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

    var isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

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

      isSubscribed = false

      ensureCanMutateNextListeners()
      var 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
    }

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

    return action
  }

老幾樣啊,先作一些判斷,咱們寫代碼的時候好像沒這麼嚴謹哈。執行reducer,觸發全部listeners。這個比較簡單。

這樣子,看起來createStore沒什麼複雜的,複雜的在哪呢?咱們掠過的中間件退出的環節。因此來燒腦吧,看看中間件

想一想咱們建立store的時候是怎麼操做的

const finalCreateStore = compose(
    applyMiddleware(thunk, logger)
  )(createStore)

  const store = finalCreateStore(rootReducer, initialState)

這種堆在一塊兒的代碼不是太好看,分開,分開

const middlewares = applyMiddleware(thunk, logger)
const composeResult = compose(middlewares)
const finalCreateStore = composeResult(createStore)
const store = finalCreateStore(rootReducer, initialState)

這就條理清晰多了,看代碼必定要看懂流程,按照順序看,否則一頭霧水,先看第一步 applyMiddleware

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

能夠看到這個方法返回一個函數,既然這個函數沒有被執行到,咱們就先不看,如今咱們獲得了一個 applyMiddleware 返回的函數了

接着看 compose 方法了

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

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

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

代碼更少,但是redux精髓全在這了。

compose 執行接收參數,若是參數個數是1,直接執行,上文的 applyMiddleware 的執行結果返回值是一個函數middlewares,做爲參數的話,長度確實是1,因此直接返回了middlewares,也就是composeResult,因此這塊是不須要compose的。而這個參數函數接收一個參數就是createStore,恰好接收createStore方法,因此咱們仍是進入到 applyMiddleware 的返回函數裏面看看

顯然 composeResult 接收到 createStore以後返回一個函數: finalCreateStore,從代碼中能夠看出也是能夠接收中間件方法的,不過應該不會有人再在這裏重複添加中間件了。

進入到 finalCreateStore 中看看

  • 建立了store,前文已經講過了

  • 把全部的middlewares執行一遍,從這裏能夠看出middlewares是一個接收 { dispatch, getState } 參數的函數,不可能有其餘狀況

  • 把middlewares執行的結果數組做爲參數再一次傳入了compose

再次進入到 compose 中看邏輯,若是隻有一箇中間件的話,一樣是把中間件直接返回,若是超過一個執行下面的邏輯

const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))

compose 一樣只是返回了一個函數。這個函數接收的參數在 applyMiddleware 裏面能看到接收到的是dispatch方法

這裏巧妙的利用了js Array的reduce方法,reduce方法的原理就是回調函數的返回值做爲後一個回調函數的第一個參數,第一個回調函數的第一個參數的值是 reduce方法的第二個參數值。

args就是dispatch方法,這裏看的出中間件函數還得返回函數,這個函數得接收相似dispatch方法的函數

看看redux-chunk這個中間件的實現吧

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;

看到 next 方法,使用過express的同窗應該會很熟悉,這個next和express的next很像,原理也相似。

每一箇中間件的最後一層函數都是一個next,才能夠在reduce裏面做爲參數傳遞,才能夠實現中間件的傳遞

這也是redux名稱的由來。

redux代碼短小精悍,設計精巧,真好。

相關文章
相關標籤/搜索