react-redux流程與實現(xiàn)分析

1、前言

redux作為一種單向數(shù)據(jù)流的實現(xiàn),配合react非常好用,尤其是在項目比較大,邏輯比較復(fù)雜的時候,單項數(shù)據(jù)流的思想能使數(shù)據(jù)的流向、變化都能得到清晰的控制,并且能很好的劃分業(yè)務(wù)邏輯和視圖邏輯。本文主要記錄下自己學(xué)習(xí)redux、react-redux時的理解。



上圖展示了redux數(shù)據(jù)的基本流程,簡單的說就是view dispatch一個action后,通過對應(yīng)reducer處理,然后更新store,最終views根據(jù)store數(shù)據(jù)的改變重新渲染界面。

2、創(chuàng)建store

store就是redux的一個數(shù)據(jù)中心,簡單的理解就是我們所有的數(shù)據(jù)都會存放在里面,然后在界面上使用時,從中取出對應(yīng)的數(shù)據(jù)。因此最開始,我們要創(chuàng)建一個這樣的store,redux提供了createStore方法。

export default function createStore(reducer, preloadedState, enhancer) {
  ...
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

可以看到createStore有三個參數(shù),返回一個對象,里面有我們常用的方法,下面一一來看一下。

(1)、getState

export default function createStore(reducer, preloadedState, enhancer) {
  ...
  var currentState = preloadedState
  function getState() {
    return currentState
  }
  ...
}

代碼只有一行,redux內(nèi)部通過currentState變量保存當(dāng)前store,變量初始值即我們調(diào)用時傳進來的preloadedState,getState()就是返回這個變量

(2)、subscribe
??代碼本身也不難,就是通過nextListeners數(shù)組保存所有的回調(diào)函數(shù),外部調(diào)用subscribe時,會將傳入的listener插入到nextListeners數(shù)組中,并返回unsubscribe函數(shù),通過此函數(shù)可以刪除nextListeners中對應(yīng)的回調(diào)。這里有個小細節(jié)是使用了currentListeners和nextListeners兩個數(shù)組來保存,主要原因是在dispatch函數(shù)中會遍歷nextListeners,這時候可能會客戶可能會繼續(xù)調(diào)用subscribe插入listener,為了保證遍歷時nextListeners不變化,需要一個臨時的數(shù)組保存。

var currentListeners = []
var nextListeners = currentListeners

function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()        //生成一個新的數(shù)組
    }
 }

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

(3)、dispatch
??我們知道dispatch一個action后,就會調(diào)用此action對應(yīng)的reducer,從下面源碼可以看的很清晰,在調(diào)用了currentReducer以后,遍歷nextListeners數(shù)組,回調(diào)所有通過subscribe注冊的函數(shù)。這樣在每次store數(shù)據(jù)更新,組件就能立即獲取到最新的數(shù)據(jù)

function dispatch(action) {  
  ...
  try {
      isDispatching = true
      currentState = currentReducer(currentState, action)  //調(diào)用reducer處理
    } finally {
      isDispatching = false
    }

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

(4)、replaceReducer
??replaceReducer是切換當(dāng)前的reducer,雖然代碼只有幾行,但是在用到時功能非常強大,它能夠?qū)崿F(xiàn)代碼熱更新的功能,即在代碼中根據(jù)不同的情況,對同一action調(diào)用不同的reducer,從而得到不同的數(shù)據(jù)。

3、中間件
??前面我們分析了createStore方法,通過createStore創(chuàng)建的store已經(jīng)可以滿足基本的分發(fā)action、修改store、更新view的功能了。但是仔細思考,對于前端必須用到的異步請求卻無能為力,因為dispatch一個異步action時,數(shù)據(jù)還沒有返回,而此時已經(jīng)調(diào)用了currentReducer()方法,這樣就沒法更新store了。這里就需要用到中間件了。

??什么是中間件呢,我的理解是:比如水從自來水廠流到用戶的家中,需要經(jīng)過過濾、消毒、凈化等一道道工藝,最終才會到用戶的家中,這中間的一道道工藝就可以理解為中間件,每個中間件都可以對水進行一些處理,當(dāng)然也可以不處理,直接讓水流過去。而我們的數(shù)據(jù)類似于水,中間件就是對數(shù)據(jù)進行特殊處理的一個個節(jié)點。redux提供了applyMiddleware方法,在分析此方法之前,先看下redux中另一個方法,compose。

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

  funcs = funcs.filter(func => typeof func === 'function')

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

這里有一個數(shù)學(xué)概念需要知道,叫柯里化函數(shù),什么是柯里化函數(shù),看一個簡單的例子

var add = function (a) {
  return function (b) {
    return a + b;
  }
}

var two = add(2)
var three = two(1)
var four = two(2)            //與 add(2)(2)效果一樣

如果我們定義一般的函數(shù)實現(xiàn)加法,那么需要傳入兩個參數(shù),而add方法每次只傳入一個參數(shù),即把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù)。
??再回過頭來看compose就會比較清晰,它類似與上面的two函數(shù),后續(xù)每次調(diào)用,都與之前傳入的2相加。而compose返回的函數(shù),在后續(xù)繼續(xù)調(diào)用時,就會遍歷調(diào)用傳入compose的funcs數(shù)組,compose就是對傳入的funcs數(shù)組進行柯里化。理解了這個方法后,再來看applyMiddleware。applyMiddleware的參數(shù)就是一系列中間件。通過compose調(diào)用每個中間件,并傳入原生dispatch方法,然后返回增強后的dispatch方法,最終返回store。由此可見applyMiddleware方法其實就是增強dispatch,這樣在使用dispatch時可以讓action流過中間件。

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

下面來看一下中間件的寫法

const funActionThunk = ({ dispatch, getState }) => next => action => {
    if (typeof action.payload === 'function') {
        return action.payload(dispatch, getState);
    }
    return next(action);
};
export default funActionThunk;

我們可以來一一看一下中間件的這些參數(shù)是從哪里傳進來的,首先{ dispatch, getState }是在applyMiddleware中傳入的middlewareAPI對象,也就是原始的dispatch和getState對象。next參數(shù)其實也是原始的dispatch,就是compose(...chain)(store.dispatch)調(diào)用時傳入的store.dispatch。action就是我們增強的dispatch分發(fā)的action。參數(shù)都傳入后,我們就可以依據(jù)不同的action進行處理了,比如上面的中間件對action.payload進行判斷,如果是函數(shù),就直接調(diào)用,如果不是,那么調(diào)用next,直至最后沒有中間件了,此時next就是原始的dispatch(即最初傳入的store.dispatch)。

4、bindActionCreators

bindActionCreators方法的目的就是簡化action的分發(fā),我們在觸發(fā)一個action時,最基本的調(diào)用是dispatch(action(param))。這樣需要在每個調(diào)用的地方都寫dispatch,非常麻煩。bindActionCreators就是將action封裝了一層,返回一個封裝過的對象,此后我們要出發(fā)action時直接調(diào)用action(param)就可以了。

5、react-redux

redux其實是一個通用的庫,它不只針對react,還可以用到其它的像vue等庫。因此react要想完美的應(yīng)用redux,還需要封裝一層,react-redux就是此作用。react-redux庫相對簡單些,它提供了一個react組件Provider和一個方法connect。下面的代碼是react-redux項目的一般寫法。

<Provider store={store} >
    <div style={{height: '100%'}}>
        <Handler/>
    </div>
</Provider>

Provider組件相當(dāng)于一個外殼,將store傳入,并提供getChildContext方法,讓后續(xù)的子組件可以通過context取到store對象。

connect方法復(fù)雜點,它返回一個函數(shù),此函數(shù)的功能是創(chuàng)建一個connect組件包在WrappedComponent組件外面,connect組件復(fù)制了WrappedComponent組件的所有屬性,并通過redux的subscribe方法注冊監(jiān)聽,當(dāng)store數(shù)據(jù)變化后,connect就會更新state,然后通過mapStateToProps方法選取需要的state,如果此部分state更新了,connect的render方法就會返回新的組件。

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
  ...
  return function wrapWithConnect(WrappedComponent) {
    ...
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容