redux源碼分析

我以爲redux對於初學者並非很友好,不少概念都不太好理解,會使用redux以後有必要看源碼javascript

createStore

redux的store存儲了應用的狀態樹,要改變state只能經過dispatch()方法。html

雖然文檔中明確指出store只能有一個,可是我在工做的項目中store有可能不止一個,好比多頁面應用。java

參數

createStore有3個參數,分別是reducerpreloadedStateenhancergit

  1. reducer。這裏傳入的reducer一般是經過combineReducers集成的reducer
  2. preloadedState。初始化store時候頗有必要傳入一個初始化的state,一來能夠給應用頁面一個初始值,二來可讓本身或者別人瞭解整個app的state結構。
  3. enhancer。一般就是一些redux的中間件,中間件的概念有點繞。

createStore作了參數校驗和類型檢測,除了reducer是必須傳入以外,其他的兩個參數都不是必須的。 傳入的形式包含如下幾種。github

  • createStore(reducer)
  • createStore(reducer, enhancer)
  • createStore(reducer, preloadedState)
  • createStore(reducer, preloadedState, enhaner)

內部實現

createStore的內部包含了3個重要的變量和4個供外部調用的方法。redux

變量

  1. currentState。存儲了整個store管理的狀態樹。
  2. currentListeners。redux實際上是觀察者模式的一個實踐。咱們能夠把currentListeners看作state變化後待執行的函數列表。
  3. isDispatching。是否正在進行dispatch
  4. nextListners。當進行subscribe操做時候,先把新的listener函數push到nextListners數組中,做爲一個最新listener數組快照。

方法

  1. getState 外部訪問store內部state的惟一方法。方法直接把currentState內部變量直接返回。
function getState() {
  return currentState;
}
複製代碼
  1. subscribe 用於增長一個listener到nextListners。每次進行dispatch方法時候,nextListenrs替換currentListeners,傳入的listener會被執行。一般來講咱們會把UI的渲染,做爲listener傳入到subscribe中,每當state變化,redux會通知UI進行render。 listener不會看到每個全部state的變化,由於state可能會在dispatch中變化屢次後,listener才被執行。 subscribe方法返回一個函數閉包,做爲取消訂閱。
function subscribe() {
  let isSubscribed = true
  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }
    isSubscribed = false
    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}
複製代碼
  1. dispatch 外部惟一能改變state的方法。調用reducer得到最新的state(reducer就是構建state樹的函數)並執行每個listener方法。 dispatch只能傳遞plain objectaction(普通的JavaScript對象),若是要傳遞一個thunk,須要使用中間件,redux-thunk。
function dispatch(action) {
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 把nextListeners替換currentListeners
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }
複製代碼
  1. replaceReducer 更換reducer方法。不多時候會使用這個方法。

combineReducers

把多個reducer合到一個函數中。combineReducers會調用每個reducer,並把每個reducer返回的state合併到state樹中。數組

參數

類型爲一個object。每一個鍵對應的必須爲一個reducer函數。bash

const params = {
  key1: reducerfunc1,
  key2: reducerfunc2,
};
複製代碼

假設reducerfunc1和reducerfunc2返回的state形式分別爲閉包

const state1 = {
  v1: '',
  v2: 0,
};

const state2 = [];
複製代碼

那麼咱們的state樹就是這樣形式,經過getState獲取到的state對象以下。app

const state = {
  key1: {
    v1: '',
    v2: 0,
  },
  key2: [],
}
複製代碼

內部實現

閉包以前的代碼都是reducer檢測

  • 過濾那些不是function的reducer(reducers的每個key必須對應的reducer方法)
  • 查看每個reducer方法是否有initState,default type是否返回state

代碼很好理解。

export default function combineReducers(reducers) {
  return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // reducer調用前的state
      const previousStateForKey = state[key]
      // reducer調用後的state
      const nextStateForKey = reducer(previousStateForKey, action)
      // 更新state樹快照
      nextState[key] = nextStateForKey
      // state是否有變更的flag。若reducer執行致使state變更,返回一個全新的state對象,因此能夠直接比較對象來判斷是否有改變。
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}
複製代碼

applyMiddleware

中間件,我以爲是整個redux中比較有意思,而且稍微有點難理解的部分。

能夠在官網查看中間件的文檔。 能夠把中間件理解爲在dispatch方法的先後作一些操做。也能夠類比爲java的切面。

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))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
複製代碼

其實這裏能夠理解爲俄羅斯套娃,原始的store.dispatch就是套娃的最裏面那個,全部的中間件按照數組的順序,一個把一個套住。而這個套娃的過程由compose完成。

要理解這部分,咱們把中間件redux-thunk的源碼也拿過來看看。 redux-thunk讓咱們的action爲函數。注意!! 本來的action只能是一個plain object。 如下是redux-thunk中間件的寫法,必須傳入store,dispatch,action後纔會執行真正的函數實體。 這裏用的是柯里化,只有參數夠了,纔會去執行函數體。

// 稍微改動過的redux-thunk
const thunk = ({ dispatch, getState }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }

  return next(action);
};

export default thunk;
複製代碼

我這裏一開始看不懂,爲何一開始dispatch賦值爲一個空函數? 其實compose完成以後會返回一個新的dispatch,這個dispatch會替換掉那個空函數

const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
複製代碼

最後返回store變量。

return {
  ...store,
  dispatch
}
複製代碼

參考

  1. redux middleware
  2. redux 源碼
相關文章
相關標籤/搜索