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