【源碼系列】redux v4.x 源碼解析

源碼系列javascript

下面是 Redux v4.x API 源碼的解讀。java

createStore

createStore(reducer, [preloadedState], enhancer)react

createStore 方法接受三個參數,第一個參數爲 reducer,第二個參數是 preloadedState (可選),第三個是參數是 enhancer。返回一個 對象 storestore 中包含方法 dispatchgetStatesubscribereplaceReducer[$$observable]git

參數 reducer 有多是一個 reducer,若是有個多個 reducer,則經過 combineReducer 函數執行後返回函數做爲 reducergithub

參數 preloadedState 表明初始狀態,不少時候都不傳,不傳該參數時候且 enhancer 是函數狀況,createStore 函數內部會把 enhancer ,做爲第二個參數使用,源碼以下:編程

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
複製代碼

參數 enhancerstore 加強器,是一個函數, createStore 做爲 enhancer 函數的參數,這裏用到了函數式編程,返回函數又傳遞 reducerpreloadState 參數,執行最終返回一個加強的 storeenhancer 常有的是 applyMiddleware() 返回值,react-devrools() 等工具,源碼以下:redux

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

    return enhancer(createStore)(reducer, preloadedState)
  }

複製代碼

介紹方法以前,下面是一些變量:api

let currentReducer = reducer   // 當前的 reducer
  let currentState = preloadedState    // 當前的 state
  let currentListeners = []   // 當前的監聽者
  let nextListeners = currentListeners  // 監聽者的備份
  let isDispatching = false  // 是否正在執行 dispatch 動做
複製代碼

dispatch

dispatch(action)數組

dispatch 方法,接受一個參數actionaction 是一個對象,該對象必須包括 type 屬性,不然會報錯。dispatch 函數內部,主要是執行了 reducer() 函數,獲得最新 state,賦值給 currentState,執行訂閱者,dispatch 函數返回一個 action 對象。源碼以下:promise

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

    if (isDispatching) {  // 若是正在 dispatch,再次觸發 dispatch,則報錯
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true  // 表示正在執行 `dispatch`
      currentState = currentReducer(currentState, action)    // 執行最新的 Reducer,返回最新的 state
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)  // 每次 dispatch 執行,訂閱者都會執行
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

複製代碼

getState

getState()

獲取最新的 state,就是返回 currentState。源碼以下:

function getState() {
    return currentState
  }
複製代碼

subscribe

添加事件訂閱者,每次觸發 dispatch(action) 時候,訂閱者都會執行。返回取消訂閱方法 unSubscribe。這裏採用了觀察者模式/發佈-訂閱者模式。源碼以下:

注意:訂閱者必須是函數,不然報錯。

function subscribe(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

    nextListeners.push(listener)     // 訂閱消息,每次 dispatch(action) 時候,訂閱者都會接受到消息

    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

      const index = nextListeners.indexOf(listener) 
      nextListeners.splice(index, 1)  // 取消訂閱者
    }
  }
複製代碼

observable

讓一個對象可被觀察。被觀察對象必須是對象,不然報錯。這裏做者用到了第三方庫 symbol-observable,對 currentState 進行觀察。源碼以下:

ECMAScript Observables 目前只是草案,還沒正式使用。

function observable() {
    const outerSubscribe = subscribe
    return {
      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())   // 對 currentState 進行監聽
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }
複製代碼

replaceReducer

替換reducer,通常用不到。源碼也簡單,把 nextReducer 賦值給 currentReducer 。源碼以下:

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

    currentReducer = nextReducer
  }
複製代碼

上面 dispatchgetStatesubscribereplaceReducer[$$observable] 這些方法使用了閉包,一直保持對 currentStatecurrentReducercurrentListenersnextListenersisDispatching 等變量的引用。好比,dispatch改變 currentState,相應的其餘方法中,currentState 也會跟着變,因此這些方法之間是存在聯繫的。

applyMiddleware

執行中間件的方法,中間件能夠有不少,有自定義的如:打印日誌,也有比較知名的 如:redux-chunkredux-promiseredux-saga 等,後續會分析。

applyMiddleware() 一般是上面提到的 createStore 方法的第三個參數 enhancer,源碼以下:

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

結合上面代碼能夠等價於

applyMiddleware(...middlewares)(createStore)(reducer, preloadedState)
複製代碼

applyMiddleware 使用了函數式編程,接收第一個參數元素爲 middlewares的數組,返回值接收createStore 爲參數的函數,返回值接收 dispatch 函數,接收一個 action(reducer, state),最終返回加強版的 store

源碼以下:

function applyMiddleware(...middlewares) {
  return createStore => (...args) => {  
    const store = createStore(...args)  // args 爲 (reducer, initialState)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
    
    const middlewareAPI = {   // 給 middleware 的 store
      getState: store.getState,
      dispatch: (...args) => dispatch(...args) 
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))  // chain 是一個參數爲 next 的匿名函數的數組(中間件執行返回的函數)
    dispatch = compose(...chain)(store.dispatch)  // 通過 compose 函數後返回的結果,是通過 middlewares 包裝後的 dispatch 

    return {   // 返回 dispatch 加強後的 store
      ...store,
      dispatch
    }
  }
}

複製代碼

compose

源碼以下:

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))) 
}
複製代碼

雖然源代碼沒幾行,做者實現方式太厲害了,若是理解起來有點費勁,能夠看看下面例子分析:

var arr = [
  function fn1(next){
    return action => {
      // next 爲 fn2 return 函數(dispatch 函數)
      const returnValue = next(action)
      return returnValue
    }
  },
  function fn2(next) {
    return action => {
      // next 爲 fn3 return 函數(dispatch 函數)
      const returnValue = next(action)
      return returnValue
    }
  },
  function fn3(next3) {
    return action => {
      // next 爲 dispatch。
      const returnValue = next3(action)
      return returnValue
    }
  },
]

var fn = arr.reduce((a, b) => (args) => a(b(args)))
fn // (args) => a(b(args)) 等價於 (dispatch) => fn1(fn2(fn3(dispatch)))

// reduce函數 執行順序:
// 第一次:
// 變量 a: ƒ fn1(args)
// 變量 b: ƒ fn2(args)
// 返回值: (args) => fn1(fn2(args))

// 第一次返回值賦值給 a
// 變量 a: (args) => fn1(fn2(args))
// 變量 b: ƒ fn3(args)

// 第二次:
// 變量 a : (args) => fn1(fn2(args))
// 變量 b: ƒ fn3(args)
// 返回值: (args) => fn1(fn2(fn3(args)))
複製代碼

因此做者的用意是 compose(f, g, h) 返回 (...args) => f(g(h(...args))),其中 f、g、h 是一個個中間件 middleware

結合上面 applyMiddleware 源碼有一段 dispatch = compose(...chain)(store.dispatch) ,因此 argsstore.dispatch(store.dispatch) => f(g(h(store.dispatch))),執行第一個中間件h(store.dispatch),返回 dispatch 函數(參數爲 action 的函數)做爲下一個中間件的參數,經過一層層的傳遞,最終返回一個通過封裝的 dispatch 函數。

下面是打印日誌中間件例子:

const logger = store => next => action => {
  console.log('dispatch', action)
  next(action)
  console.log('finish', action)
}

const logger2 = store => next2 => action2 => {
  console.log('dispatch2', action2)
  next2(action2)
  console.log('finish2', action2)
}
...
const store = createStore(rootReducer, applyMiddleware(logger, logger2))
複製代碼

注意:但若是在某個中間件中使用了 store.dipsatch(),而不是 next(),那麼就會回到起始位置,會形成無限循環了。

每次觸發一個 dispatch,中間件都會執行,打印的順序爲 dispatch distapch2 finish2 finish

combineReducer

這個 API 理解爲:若是有多個 reducer,那麼須要把它們合成一個 reducer,在傳給函數 createStore(reducer)

核心源碼以下:

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]  // reducer 必定是函數
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  return function combination(state = {}, action) {  // 返回一個新的 reducer 函數
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) { // 每次執行一次 dipatch(action),全部的 reducer 都會執行
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state  // 判斷 state 是否有變化,若是有則返回新的 state
  }
}

複製代碼

bindActionCreators

這個 API 能夠理解爲:生成 action 的方法。主要是把 dispatch 也封裝進 bindActionCreator(actionCreator, dispatch) 方法裏面去,因此調用時候能夠直接觸發 dispatch(action),不須要在手動調用 dispatch,好比 dispatch(fetchPeople({type: TYPE, text: 'fetch people'})),使用這個 API 後,則直接 fetchPeople({type: TYPE, text: 'fetch people'})

源碼以下:

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

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

  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

複製代碼

參考

交流

下面是博客地址,以爲不錯點個Star,謝謝~

github.com/hankzhuo/Bl…

相關文章
相關標籤/搜索