Redux源碼簡析

開始

今天分析的源碼就是大名鼎鼎的Redux,有React的地方就有它的身影,一開始感受這麼有名的庫,代碼應該挺多挺複雜的,可是出乎我意料的是,它的實現很是簡潔,核心代碼可能也就在兩百行左右,我彷佛明白它一直流行的緣由,不是實現有多複雜精妙而是一種簡潔,keep it simple!ios

分析

首先先熟悉一個很重要的概念:結構共享,其實也很簡單,例如假設有一個這樣的狀態樹:
AAD2D192-5ADA-4EFB-B6C0-B964A7CA0E28.png
當咱們把狀態C換成狀態E, 那麼就從新生成一棵狀態樹:
57FC3838-D6D6-4912-9D15-5FC07DBB9B47.png
能夠看到新的樹裏面全部節點都並不都是新建立的,只有E,C',Root'是新建的節點,而B和D都是引用本來的節點,這就是所謂的結構共享,當某個狀態節點修改的時候,一路往上直到根節點,都須要從新建立;因此當咱們拿到新的狀態樹引用的時候,只要經過遍歷和簡單的比較引用就能夠發現狀態樹那些部分是修改過,而最終能夠定位到被修改過的節點的而後進行相應的更新。redux

明白了主要的概念以後,咱們接着代碼分析。
直接從一段代碼開始入手吧:axios

import { createStore, combineReducer applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';

const store = createStore(
  combineReducers({
    cart: (state = [], action)=> {
        if(action.type === 'ADD_PRODUCT') {
            return [
                ...state,
                action.product
            ]
        }
        return state;
    }
    user: combineReducers({
        info: (state = {}, action)=> {
            if(action.type === 'UPDATE_INFO') {
                return action.info;
            }
            return state;
        }
    })
  }),
  applyMiddleware(thunk, createLogger())
);

咱們建立了一個store,而裏面的狀態有兩個,一個是cart(購物車),而它響應的action也只有一個就是添加商品;而另一個是user,它的目的是爲了分析嵌套的combineReducers。數組

好吧,準備工做完成,按照代碼調用順序先分析combineReducers函數:app

export default 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]
    }
  }
  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]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

首先combineReducers返回的也是reducer,正如名稱同樣,它是能夠組合其餘reducer的reducer;
能夠看到combineReducers會先遍歷reducers主要爲了找出對象裏面的reducer(只要是function都判斷爲是reducer),而後返回一個能夠接受state和action的函數combination;
再來分析combination,當接收到根state和觸發的action後,先初始化一個默認的nextState對象(因此combineReducers只能傳入一個對象,不能是數組)用來接收子reducer產生的state,而後就開始遍歷finalReducerKeys依次調用子reducer,並傳人對應的子state和action,若是子reducer根據action判斷後返回了一個新的子state,那麼就把hasChanged設置爲true代表有子state改變了,最後返回的時候會根據這個標記位來判斷是否要返回新的nextState,仍是舊的state。
這樣這個函數其實就已經完成從舊的state樹生成一棵新的state樹而且可以實現剛纔所說的結構共享。
代碼十分簡短,能夠說是一目瞭然,固然它的state樹的維護是要靠每一個reducer遵循一個協議,有新的改變的時候必需要返回一個新的對象,就像一開始的初始化代碼那樣,若是添加商品的時候是在原來的數組上添加的並無新建立數組,就能觸發新的state樹生成了,由於combineReducers這裏沒法知道子state發生變化了。異步

接着看createStore函數:函數

function createStore(reducer, preloadedState, enhancer) {
    let currentReducer = reducer
    let currentState = preloadedState
    function dispatch(action) {
        ...

        try {
          isDispatching = true
          currentState = currentReducer(currentState, action)
        } finally {
          isDispatching = false
        }

        ...

        return action
  }
  
  dispatch({ type: ActionTypes.INIT })
  ...
}

感受createStore這個方法其實也很簡單了,由於剛剛combineReducers其實已經完成了最核心的工做了,createStore初始化的時候只須要dispach一個INIT的action而後就是調用根reducer,傳入初始化的state和action產生新的根state,初始化的工做就完成了。this

最後再分析一下Redux的中間件機制的實現吧,對於中間件機制的實現,axios利用的是Promise鏈,而Redux則是利用簡單的函數回調。
直接看applyMiddleware函數的實現:spa

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
    }
  }
}

首先這個函數對於dispatch變量的處理,一開始dispatch綁定的是一個直接拋出錯誤的方法,目的是爲了阻止開發者在構建中間件的時候調用dispatch,當中間件鏈構建完以後dispatch才綁定最後生成的中間件鏈;因此後面在中間件裏面調用的dispatch其實都會從新走一遍中間件鏈。code

那麼中間件鏈是如何構建出來的尼,關鍵點就是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)))
}

正如源碼註釋所說舉的例子,compose的做用就是能夠把compose(f, g, h)轉變成(...args) => f(g(h(...args)))一個這樣的函數;可是這樣看好像跟中間件鏈徹底靠不上邊,只是僅僅簡單把h函數返回的值傳給g函數,g函數又把本身調用的結果傳給f函數,徹底不是f->g->h這樣的鏈式調用。可是若是f,g,h是如下這樣的定義形式那就徹底不同了:

(next)=> (params)=> {
    next();
}

h會把一個函數做爲g的參數next傳給g,g又會生成一個函數做爲f的參數next傳給f,就可以實現f->g->h這樣的鏈式調用了。

再看看redux-thunk這個中間件的怎麼定義的:

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;

({ dispatch, getState }) => next => action => {}
dispatch和getState參數會在applyMiddleware函數中獲取:

const chain = middlewares.map(middleware => middleware(middlewareAPI))

而後就是返回一個剛剛所說的相似函數定義形式。
而redux-thunk的做用就是判斷若是action是一個函數的話,就把dispatch和getState傳給它,而且停止後面的中間件鏈式調用,這就阻止最後真正的dispatch調用了,而action函數裏面也接收到dispatch函數能夠進行異步或同步dispatch。

最後

Redux的分析就結束了,整體來講這個庫真的簡短有力,在分析的過程當中確確實實的感覺到一種優雅,如同詩通常,何時個人代碼也能作到這樣尼,路漫漫其修遠兮。。。

相關文章
相關標籤/搜索