摘要:在得到新的狀態(tài)后,依次調(diào)用所有的監(jiān)聽(tīng)器,通知狀態(tài)的變更。執(zhí)行完后,獲得數(shù)組,它保存的對(duì)象是第二個(gè)箭頭函數(shù)返回的匿名函數(shù)。部分源碼利用這個(gè)屬性,所有子組件均可以拿到這個(gè)屬性。
Redux使用中的幾個(gè)點(diǎn):
Redux三大設(shè)計(jì)原則
Create Store
Redux middleware
combineReducer
Provider與Connect
Redux流程梳理
Redux設(shè)計(jì)特點(diǎn)
1. Redux三大設(shè)計(jì)原則 1. 單一數(shù)據(jù)源在傳統(tǒng)的 MVC 架構(gòu)中,我們可以根據(jù)需要?jiǎng)?chuàng)建無(wú)數(shù)個(gè) Model,而 Model 之間可以互相監(jiān)聽(tīng)、觸發(fā)事件甚至循環(huán)或嵌套觸發(fā)事件,這些在 Redux 中都是不允許的。因?yàn)樵?Redux 的思想里,一個(gè)應(yīng)用永遠(yuǎn)只有唯一的數(shù)據(jù)源。
實(shí)際上,使用單一數(shù)據(jù)源的好處在于整個(gè)應(yīng)用狀態(tài)都保存在一個(gè)對(duì)象中,這樣我們隨時(shí)可以提取出整個(gè)應(yīng)用的狀態(tài)進(jìn)行持久化(比如實(shí)現(xiàn)一個(gè)針對(duì)整個(gè)應(yīng)用的即時(shí)保存功能)。此外,這樣的設(shè)計(jì)也為服務(wù)端渲染提供了可能。
在 Redux 中,我們并不會(huì)自己用代碼來(lái)定義一個(gè) store。取而代之的是,我們定義一個(gè) reducer,它的功能是根據(jù)當(dāng)前觸發(fā)的 action 對(duì)當(dāng)前應(yīng)用的狀態(tài)(state)進(jìn)行迭代,這里我們并沒(méi)有直接修改應(yīng)用的狀態(tài),而是返回了一份全新的狀態(tài)。
Redux 提供的 createStore 方法會(huì)根據(jù) reducer 生成 store。最后,我們可以利用 store. dispatch
方法來(lái)達(dá)到修改狀態(tài)的目的。
在 Redux 里,我們通過(guò)定義 reducer 來(lái)確定狀態(tài)的修改,而每一個(gè) reducer 都是純函數(shù),這意味著它沒(méi)有副作用,即接受一定的輸入,必定會(huì)得到一定的輸出。
這樣設(shè)計(jì)的好處不僅在于 reducer 里對(duì)狀態(tài)的修改變得簡(jiǎn)單、純粹、可測(cè)試,更有意思的是,Redux 利用每次新返回的狀態(tài)生成酷炫的時(shí)間旅行(time travel)調(diào)試方式,讓跟蹤每一次因?yàn)橛|發(fā) action 而改變狀態(tài)的結(jié)果成為了可能。
2.Create Store我們從store的誕生開(kāi)始說(shuō)起。create store函數(shù)API文檔如下:
createStore(reducer, [initialState], enhancer)
可以看出,它接受三個(gè)參數(shù):reducer、initialState 和 enhancer 。Store enhancer 是一個(gè)組合 store creator 的高階函數(shù),返回一個(gè)新的強(qiáng)化過(guò)的 store creator。這與 middleware 相似,它也允許你通過(guò)復(fù)合函數(shù)改變 store 接口。
再來(lái)看看他的返回值:
{ dispatch: f (action), getState: f (), replaceReducer: f (nextReducer), subscribe: f (listener), Symbol(observable): f () }
store的返回值就是一個(gè)普通對(duì)象,里面有幾個(gè)常用的方法:
dispatch:就是我們最常用的dispatch方法,派發(fā)action。
getState:通過(guò)該方法,我們可以拿到當(dāng)前狀態(tài)樹(shù)state。
replaceReducer:這個(gè)方法主要用于 reducer 的熱替換,下面介紹該方法。
subscribe:添加一個(gè)變化監(jiān)聽(tīng)器。每當(dāng) dispatch(action)的時(shí)候就會(huì)執(zhí)行,state 樹(shù)中的一部分可能已經(jīng)變化。
observable:觀察者模式,用于處理訂閱關(guān)系。
這里挑幾個(gè)方法介紹:
getState在完成基本的參數(shù)校驗(yàn)之后,在 createStore 中聲明如下變量及 getState 方法:
var currentReducer = reducer var currentState = initialState var listeners = [] // 當(dāng)前監(jiān)聽(tīng) store 變化的監(jiān)聽(tīng)器 var isDispatching = false // 某個(gè) action 是否處于分發(fā)的處理過(guò)程中 /** * Reads the state tree managed by the store. * * @returns {any} The current state tree of your application. */ function getState() { return currentState }
getState方法就是簡(jiǎn)單返回當(dāng)前state,如果state沒(méi)有被reducer處理過(guò),他就是initialState。
subscribe在 getState 之后,定義了 store 的另一個(gè)方法 subscribe:
function subscribe(listener) { listeners.push(listener) var isSubscribed = true return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false var index = listeners.indexOf(listener) listeners.splice(index, 1) } }
Store 允許使用store.subscribe方法設(shè)置監(jiān)聽(tīng)函數(shù),一旦 State 發(fā)生變化,就自動(dòng)執(zhí)行這個(gè)函數(shù)。
顯然,只要把 View 的更新函數(shù)(對(duì)于 React 項(xiàng)目,就是組件的render方法或setState方法)放入listen,就會(huì)實(shí)現(xiàn) View 的自動(dòng)渲染。你可能會(huì)感到奇怪,好像我們?cè)?Redux 應(yīng)用中并沒(méi)有使用 store.subscribe 方法?事實(shí)上,
React Redux 中的 connect 方法隱式地幫我們完成了這個(gè)工作。
store.subscribe方法返回一個(gè)函數(shù),調(diào)用這個(gè)函數(shù)就可以解除監(jiān)聽(tīng)。
dispatchdispatch是redux的核心方法:
function dispatch(action) { if (!isPlainObject(action)) { throw new Error( "Actions must be plain objects. " + "Use custom middleware for async actions." ) } if (typeof action.type === "undefined") { throw new Error( "Actions may not have an undefined "type" property. " + "Have you misspelled a constant?" ) } if (isDispatching) { throw new Error("Reducers may not dispatch actions.") } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } listeners.slice().forEach(listener => listener()) return action }
判斷當(dāng)前是否處于某個(gè) action 的分發(fā)過(guò)程中,這個(gè)檢查主要是為了避免在 reducer 中分發(fā) action 的情況,因?yàn)檫@樣做可能導(dǎo)致分發(fā)死循環(huán),同時(shí)也增加了數(shù)據(jù)流動(dòng)的復(fù)雜度。
確認(rèn)當(dāng)前不屬于分發(fā)過(guò)程中后,先設(shè)定標(biāo)志位,然后將當(dāng)前的狀態(tài)和 action 傳給當(dāng)前的reducer,用于生成最新的 state。這看起來(lái)一點(diǎn)都不復(fù)雜,這也是我們反復(fù)強(qiáng)調(diào)的 reducer 工作過(guò)程——純函數(shù)、接受狀態(tài)和 action 作為參數(shù),返回一個(gè)新的狀態(tài)。
在得到新的狀態(tài)后,依次調(diào)用所有的監(jiān)聽(tīng)器,通知狀態(tài)的變更。需要注意的是,我們?cè)谕ㄖO(jiān)聽(tīng)器變更發(fā)生時(shí),并沒(méi)有將最新的狀態(tài)作為參數(shù)傳遞給這些監(jiān)聽(tīng)器。這是因?yàn)樵诒O(jiān)聽(tīng)器中,我們可以直接調(diào)用 store.getState() 方法拿到最新的狀態(tài)。
最終,處理之后的 action 會(huì)被 dispatch 方法返回。
replaceReducerfunction replaceReducer(nextReducer) { if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function."); } currentReducer = nextReducer; dispatch({ type: ActionTypes.INIT }); }
這是為了拿到所有 reducer 中的初始狀態(tài)(你是否還記得在定義 reducer 時(shí),第一個(gè)參數(shù)為previousState,如果該參數(shù)為空,我們提供默認(rèn)的 initialState)。只有所有的初始狀態(tài)都成功獲取后,Redux 應(yīng)用才能有條不紊地開(kāi)始運(yùn)作。
3.Redux middlewareIt provides a third-party extension point between dispatching an action, and the moment it reaches
the reducer
它提供了一個(gè)分類(lèi)處理 action 的機(jī)會(huì)。在middleware 中,你可以檢閱每一個(gè)流過(guò)的 action,挑選出特定類(lèi)型的action 進(jìn)行相應(yīng)操作,給你一次改變 action 的機(jī)會(huì)。
常規(guī)的同步數(shù)據(jù)流模式的流程圖如下:
不同業(yè)務(wù)需求下,比如執(zhí)行action之前和之后都要打log;action觸發(fā)一個(gè)異步的請(qǐng)求,請(qǐng)求回來(lái)之后渲染view等。需要為這一類(lèi)的action添加公共的方法或者處理,使用redux middleware流程圖如下:
每一個(gè) middleware 處理一個(gè)相對(duì)獨(dú)立的業(yè)務(wù)需求,通過(guò)串聯(lián)不同的 middleware 實(shí)現(xiàn)變化多樣的功能。比如上面的業(yè)務(wù),我們把處理log的代碼封裝成一個(gè)middleware,處理異步的也是一個(gè)middleware,兩者串聯(lián),卻又相互獨(dú)立。
使用middleware之后,action觸發(fā)的dispatch并不是原來(lái)的dispatch,而是經(jīng)過(guò)封裝的new dispatch,在這個(gè)new dispatch中,按照順序依次執(zhí)行每個(gè)middleware,最后調(diào)用原生的dispatch。
我們來(lái)看下logger middleware如何實(shí)現(xiàn)的:
export default store => next => action => { console.log("dispatch:", action); next(action); console.log("finish:", action); }
這里代碼十分簡(jiǎn)潔,就是在next調(diào)用下一個(gè)middleware之前和之后,分別打印兩次。
Redux 提供了 applyMiddleware 方法來(lái)加載 middleware,該方法的源碼如下:
import compose from "./compose"; export default function applyMiddleware(...middlewares) { return function (next) { return function (reducer, initialState) { let store = next(reducer, initialState); let dispatch = store.dispatch; let chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action), }; chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch, }; } } }
其中compose源碼如下:
function compose(...funcs) { return arg => funcs.reduceRight((composed, f) => f(composed), arg); }
使用的時(shí)候,如下:
const newStore = applyMiddleware([mid1, mid2, mid3, ...])(createStore)(reducer, initialState);
ok,相關(guān)源碼已就位,我們來(lái)詳細(xì)解析一波。
函數(shù)式編程思想設(shè)計(jì) :middleware 的設(shè)計(jì)有點(diǎn)特殊,是一個(gè)層層包裹的匿名函數(shù),這其實(shí)是函數(shù)式編程中的
currying,它是一種使用匿名單參數(shù)函數(shù)來(lái)實(shí)現(xiàn)多參數(shù)函數(shù)的方法。applyMiddleware 會(huì)對(duì) logger 這個(gè)middleware 進(jìn)行層層調(diào)用,動(dòng)態(tài)地將 store 和 next 參數(shù)賦值。currying 的 middleware 結(jié)構(gòu)的好處主要有以下兩點(diǎn)。
易串聯(lián):currying 函數(shù)具有延遲執(zhí)行的特性,通過(guò)不斷 currying 形成的 middleware 可以累積參數(shù),再配合組合(compose)的方式,很容易形成 pipeline 來(lái)處理數(shù)據(jù)流。
? 共享 store: 在 applyMiddleware 執(zhí)行的過(guò)程中,store 還是舊的,但是因?yàn)殚]包的存在,applyMiddleware 完成后,所有的 middleware 內(nèi)部拿到的 store 是最新且相同的。
給 middleware 分發(fā) store:newStore創(chuàng)建完成之后,applyMiddleware 方法陸續(xù)獲得了3個(gè)參數(shù),第一個(gè)是 middlewares 數(shù)組[mid1, mid2, mid3, ...],第二個(gè)是 Redux 原生的 createStore ,最后一個(gè)是 reducer。然后,我們可以看到 applyMiddleware 利用 createStore 和 reducer 創(chuàng)建了一個(gè) store。而 store 的 getState方法和 dispatch 方法又分別被直接和間接地賦值給 middlewareAPI 變量 store:
const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action), }; chain = middlewares.map(middleware => middleware(middlewareAPI));
然后,讓每個(gè) middleware 帶著 middlewareAPI 這個(gè)參數(shù)分別執(zhí)行一遍。執(zhí)行完后,獲得 chain數(shù)組 [f1, f2, ... , fx, ..., fn],它保存的對(duì)象是第二個(gè)箭頭函數(shù)返回的匿名函數(shù)。因?yàn)槭情]包,每個(gè)匿名函數(shù)都可以訪(fǎng)問(wèn)相同的 store,即 middlewareAPI。
middlewareAPI 中的 dispatch 為什么要用匿名函數(shù)包裹呢?我們用 applyMiddleware 是為了改造 dispatch,所以 applyMiddleware 執(zhí)行完后,dispatch 是變化了的,而 middlewareAPI 是 applyMiddleware 執(zhí)行中分發(fā)到各個(gè) middleware 的,所以必須用匿名函數(shù)包裹 dispatch,這樣只要 dispatch 更新了,middlewareAPI 中的 dispatch 應(yīng)用也會(huì)發(fā)生變化。
組合串聯(lián) middleware:這一層只有一行代碼,卻是 applyMiddleware 精華之所在dispatch = compose(...chain)(store.dispatch); ,其中 compose 是函數(shù)式編程中的組合,它將 chain 中的所有匿名函數(shù) [f1, f2, ... , fx, ..., fn]組裝成一個(gè)新的函數(shù),即新的 dispatch。當(dāng)新 dispatch 執(zhí)行時(shí),[f1, f2, ... , fx, ..., fn],從右到左依次執(zhí)行。
compose(...funcs) 返回的是一個(gè)匿名函數(shù),其中 funcs 就是 chain 數(shù)組。當(dāng)調(diào)用 reduceRight時(shí),依次從 funcs 數(shù)組的右端取一個(gè)函數(shù) fx 拿來(lái)執(zhí)行,fx 的參數(shù) composed 就是前一次 fx+1 執(zhí)行的結(jié)果,而第一次執(zhí)行的 fn(n 代表 chain 的長(zhǎng)度)的參數(shù) arg 就是 store.dispatch。所以,當(dāng) compose 執(zhí)行完后,我們得到的 dispatch 是這樣的,假設(shè) n = 3:
dispatch = f1(f2(f3(store.dispatch))));
這時(shí)調(diào)用新 dispatch,每一個(gè) middleware 就依次執(zhí)行了。
在 middleware 中調(diào)用 dispatch 會(huì)發(fā)生什么:經(jīng)過(guò) compose 后,所有的 middleware 算是串聯(lián)起來(lái)了。可是還有一個(gè)問(wèn)題,在分發(fā) store 時(shí),我們提到過(guò)每個(gè) middleware 都可以訪(fǎng)問(wèn) store,即 middlewareAPI 這個(gè)變量,也可以拿到 store 的dispatch 屬性。那么,在 middleware 中調(diào)用 store.dispatch() 會(huì)發(fā)生什么,和調(diào)用 next() 有區(qū)別嗎?現(xiàn)在我們來(lái)說(shuō)明兩者的不同:
const logger = store => next => action => { console.log("dispatch:", action); next(action); console.log("finish:", action); }; const logger = store => next => action => { console.log("dispatch:", action); store.dispatch(action); console.log("finish:", action); };
在分發(fā) store 時(shí)我們解釋過(guò),middleware 中 store 的 dispatch 通過(guò)匿名函數(shù)的方式和最終compose 結(jié)束后的新 dispatch 保持一致,所以,在 middleware 中調(diào)用 store.dispatch() 和在其他任何地方調(diào)用的效果一樣。而在 middleware 中調(diào)用 next(),效果是進(jìn)入下一個(gè) middleware,下圖就是redux middleware最著名的洋蔥模型圖。
如果一個(gè)項(xiàng)目過(guò)大,我們通常按模塊來(lái)寫(xiě)reducer,但是redux create store只接受一個(gè)reducer參數(shù),所以我們需要合并reducer。這里就用到了redux提供的combineReducer輔助函數(shù):
combineReducers({ layout, home, ...asyncReducers })
這個(gè)函數(shù)用起來(lái)很簡(jiǎn)單,就是傳入一個(gè)對(duì)象,key是模塊reducer對(duì)應(yīng)的名字, 值是對(duì)應(yīng)reducer。值是一個(gè)function,相當(dāng)于是一個(gè)新的reducer,源碼如下:
export default function combineReducers(reducers) { var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var key = reducerKeys[i] if (process.env.NODE_ENV !== "production") { if (typeof reducers[key] === "undefined") { warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === "function") { finalReducers[key] = reducers[key] } } var finalReducerKeys = Object.keys(finalReducers) if (process.env.NODE_ENV !== "production") { var unexpectedKeyCache = {} } var sanityError try { assertReducerSanity(finalReducers) } catch (e) { sanityError = e } return function combination(state = {}, action) { if (sanityError) { throw sanityError } if (process.env.NODE_ENV !== "production") { var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } var hasChanged = false var nextState = {} for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] var previousStateForKey = state[key] var nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === "undefined") { var errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
源碼不是很多,除去一些驗(yàn)證代碼,剩下的就是說(shuō):return一個(gè)function,我們暫時(shí)稱(chēng)呼他combination,就相當(dāng)于是與一個(gè)總的reducer,每次action都會(huì)走到combination中,combination會(huì)遍歷輸入的reducer,將action放到每個(gè)reducer中執(zhí)行一下,計(jì)算出返回結(jié)果就是nextState,nextState于previousState如果!==說(shuō)明改變了,返回nextState,否則返回執(zhí)行之前的state。
這也解釋了不同模塊actionType如果相同的話(huà),兩個(gè)模塊的reducer都會(huì)走一遍的問(wèn)題,在actionType名稱(chēng)前面加上模塊前綴即可解決問(wèn)題。
5. Provider與ConnectProvider與Connet組件都是React-Redux提供的核心組件,兩者看起來(lái)功能一樣,都是幫助容器組件獲取store中的數(shù)據(jù),但是原理與功能卻不同。
ProviderProvider組件在所有組件的最外層,其接受store作為參數(shù),將store里的state使用context屬性向下傳遞。部分源碼:
export default class Provider extends Component { getChildContext() { return { store: this.store } } constructor(props, context) { super(props, context) this.store = props.store } render() { const { children } = this.props return Children.only(children) } }
利用context這個(gè)屬性,Provider所有子組件均可以拿到這個(gè)屬性。
Connectconnect實(shí)現(xiàn)的功能是將需要關(guān)聯(lián)store的組件和store的dispatch等數(shù)據(jù)混合到一塊,這塊就是一個(gè)高階組件典型的應(yīng)用:
import hoistStatics from "hoist-non-react-statics" export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { // ... return function wrapWithConnect(WrappedComponent) { // ... class Connect extends Component { // ... render() { // ... if (withRef) { this.renderedElement = createElement(WrappedComponent, { ...this.mergedProps, ref: "wrappedInstance" }) } else { this.renderedElement = createElement(WrappedComponent, this.mergedProps ) } return this.renderedElement } } // ... return hoistStatcis(Connect, WrappedComponent); } }
還是先從他的四個(gè)參數(shù)說(shuō)起:
1.mapStateToPropsconnect 的第一個(gè)參數(shù)定義了我們需要從 Redux 狀態(tài)樹(shù)中提取哪些部分當(dāng)作 props 傳給當(dāng)前組件。一般來(lái)說(shuō),這也是我們使用 connect 時(shí)經(jīng)常傳入的參數(shù)。事實(shí)上,如果不傳入這個(gè)參數(shù),React 組件將永遠(yuǎn)不會(huì)和 Redux 的狀態(tài)樹(shù)產(chǎn)生任何關(guān)系。具體在源代碼中的表現(xiàn)為:
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { const shouldSubscribe = Boolean(mapStateToProps) // ... class Connect extends Component { // ... trySubscribe() { if (shouldSubscribe && !this.unsubscribe) { this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange() } } // ... } }
mapStateToProps會(huì)訂閱 Store,每當(dāng)state更新的時(shí)候,就會(huì)自動(dòng)執(zhí)行,重新計(jì)算 UI 組件的參數(shù),從而觸發(fā) UI 組件的重新渲染。
mapStateToProps的第一個(gè)參數(shù)總是state對(duì)象,還可以使用第二個(gè)參數(shù),代表容器組件的props對(duì)象。
這塊的源碼相對(duì)較簡(jiǎn)單:
const mapState = mapStateToProps || defaultMapStateToProps class Connect extends Component { computeStateProps(store, props) { if (!this.finalMapStateToProps) { return this.configureFinalMapState(store, props) } const state = store.getState() const stateProps = this.doStatePropsDependOnOwnProps ? this.finalMapStateToProps(state, props) : this.finalMapStateToProps(state) if (process.env.NODE_ENV !== "production") { checkStateShape(stateProps, "mapStateToProps") } return stateProps } configureFinalMapState(store, props) { const mappedState = mapState(store.getState(), props) const isFactory = typeof mappedState === "function" this.finalMapStateToProps = isFactory ? mappedState : mapState this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1 if (isFactory) { return this.computeStateProps(store, props) } if (process.env.NODE_ENV !== "production") { checkStateShape(mappedState, "mapStateToProps") } return mappedState } }
這塊原理很簡(jiǎn)單,進(jìn)行一些參數(shù)校驗(yàn),判斷第一個(gè)參數(shù)mapStateToProps返回值是否為function,如果是遞歸調(diào)用,不是的話(huà)算出返回值。如果沒(méi)傳這個(gè)參數(shù),默認(rèn)給{}。
我們可能會(huì)疑惑為什么傳給 connect 的第一個(gè)參數(shù)本身是一個(gè)函數(shù),react-redux 還允許這個(gè)函數(shù)的返回值也是一個(gè)函數(shù)呢?
簡(jiǎn)單地說(shuō),這樣設(shè)計(jì)可以允許我們?cè)?connect 的第一個(gè)參數(shù)里利用函數(shù)閉包進(jìn)行一些復(fù)雜計(jì)算的緩存,從而實(shí)現(xiàn)效率優(yōu)化的目的
當(dāng)我們使用的時(shí)候:
const mapStateToProps = (state, props) => ({ home: state.home, layout: state.layout });
使用ownProps作為參數(shù)后,如果容器組件的參數(shù)發(fā)生變化,也會(huì)引發(fā) UI 組件重新渲染
2.mapDispatchToProps人如其名,它接受 store 的 dispatch 作為第一個(gè)參數(shù),同時(shí)接受 this.props 作為可選的第二個(gè)參數(shù)。利用這個(gè)方法,我們可以在 connect 中方便地將 actionCreator 與 dispatch 綁定在一起(利用 bindActionCreators 方法),最終綁定好的方法也會(huì)作為 props 傳給當(dāng)前組件。這塊的源碼與mapStateToProps一樣,就不貼了。
bindActionCreator
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) }3.mergeProps
前兩個(gè)參數(shù)返回的對(duì)象,都要跟組件自身的props merge一下,形成一個(gè)新的對(duì)象賦值給對(duì)應(yīng)組件,我們可以在這一步做一些處理,這個(gè)參數(shù)就是干這個(gè)的,該參數(shù)簽名:
mergeProps(stateProps, dispatchProps, ownProps): props
默認(rèn)情況如果沒(méi)傳該參數(shù),返回Object.assign(ownProps, stateProps, dispatchProps)。
4.options如果指定這個(gè)參數(shù),可以定制 connector 的行為。
[pure = true] (Boolean): 如果為 true,connector 將執(zhí)行 shouldComponentUpdate 并且淺對(duì)比 mergeProps 的結(jié)果,避免不必要的更新,前提是當(dāng)前組件是一個(gè)“純”組件,它不依賴(lài)于任何的輸入或 state 而只依賴(lài)于 props 和 Redux store 的 state。默認(rèn)值為 true。
[withRef = false] (Boolean): 如果為 true,connector 會(huì)保存一個(gè)對(duì)被包裝組件實(shí)例的引用,該引用通過(guò) getWrappedInstance() 方法獲得。默認(rèn)值為 false。
這個(gè)connect組件還干了一件事,狀態(tài)緩存判斷。當(dāng)store變了的時(shí)候,前后狀態(tài)判斷,如果狀態(tài)不等,更新組件,并且完成事件分發(fā)。
6. Redux流程梳理上面講了大量的函數(shù)源碼,這么些函數(shù)之間的關(guān)系:
初始化階段:
createStore創(chuàng)建一個(gè)store對(duì)象
將store對(duì)象通過(guò)參數(shù)給Provider組件
Provider組件將store通過(guò)context向子組件傳遞
Connect組件通過(guò)context獲取到store,存入自己的state
componentDidMount里面訂閱store.subscribe事件
更新數(shù)據(jù)階段:
用戶(hù)事件觸發(fā)
actionCreator生成action交給dispatch
實(shí)際上交給了封裝后的中間層(compose(applyMiddleware(...)))
請(qǐng)求依次通過(guò)每個(gè)中間件,中間件通過(guò)next進(jìn)行下一步
最后一個(gè)中間件將action交給store.dispatch
dispatch內(nèi)部將action交給reducer執(zhí)行
combineReducer將每個(gè)子reducer執(zhí)行一遍算出新的state
dispatch內(nèi)部調(diào)用所有訂閱事件
Connect組件handleChange事件觸發(fā)判斷新state和舊state是否===
并且判斷新的state是否與mapStateToProps shallowEqual
不等則setState觸發(fā)更新
7.Redux設(shè)計(jì)技巧匿名函數(shù)&&閉包使用
redux核心函數(shù)大量使用了匿名函數(shù)和閉包來(lái)實(shí)現(xiàn)數(shù)據(jù)共享和狀態(tài)同步。
函數(shù)柯里化使用
使用函數(shù)柯里化s實(shí)現(xiàn)參數(shù)復(fù)用,本質(zhì)上是降低通用性,提高適用性。
核心狀態(tài)讀取是拷貝而不是地址
對(duì)于state這種核心狀態(tài)使用getState()計(jì)算出新的state,而不是直接返回一個(gè)state對(duì)象。
觀察者訂閱者是核心實(shí)現(xiàn)
使用觀察者訂閱者模式實(shí)現(xiàn)數(shù)據(jù)響應(yīng)。
context這個(gè)api的使用
平時(shí)開(kāi)發(fā)不常接觸的api實(shí)現(xiàn)Provider與Connect通信。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/93471.html
摘要:背景如今在如此復(fù)雜的前端交互和邏輯中,如果是單靠框架,,是遠(yuǎn)遠(yuǎn)不夠的,還需要一個(gè)對(duì)內(nèi)部的數(shù)據(jù)狀態(tài)進(jìn)行管理的機(jī)制才行,而對(duì)于這種數(shù)據(jù)管理機(jī)制如今較為熱門(mén)是主要有幾個(gè),,這次主要講述的就是對(duì)源碼進(jìn)行分析。 由于這段時(shí)間一直很忙,所以本想六月初研究完redux源碼就寫(xiě)一篇博客記錄一下自己的心得,但一直現(xiàn)在才空閑出來(lái),廢話(huà)不多說(shuō)了,直接說(shuō)主題吧。 背景:如今在如此復(fù)雜的前端交互和邏輯中,如果是...
摘要:歡迎關(guān)注源碼分析系列文章源碼分析之一源碼分析之二源碼分析之三源碼分析之四源碼分析之五文件算是非常簡(jiǎn)單的一個(gè)文件了,該文件就實(shí)現(xiàn)一個(gè)目的以前這樣觸發(fā)一個(gè),即,現(xiàn)在變成這樣觸發(fā)一個(gè)。目的很單純,簡(jiǎn)化某個(gè)的調(diào)用。 歡迎關(guān)注redux源碼分析系列文章:redux源碼分析之一:createStore.jsredux源碼分析之二:combineReducers.jsredux源碼分析之三:bind...
摘要:歡迎關(guān)注源碼分析系列文章源碼分析之一源碼分析之二源碼分析之三源碼分析之四源碼分析之五文件對(duì)外暴露了一個(gè)函數(shù),函數(shù)是的一個(gè)輔助性的函數(shù),用于拆分里面的第一個(gè)參數(shù)函數(shù)。函數(shù)的返回值是一個(gè)函數(shù),該函數(shù)是組合之后的一個(gè)標(biāo)準(zhǔn)的函數(shù)。 歡迎關(guān)注redux源碼分析系列文章:redux源碼分析之一:createStore.jsredux源碼分析之二:combineReducers.jsredux源碼分...
摘要:的中間件主要是通過(guò)模塊實(shí)現(xiàn)的。返回的也是一個(gè)對(duì)象這個(gè)其實(shí)就是,各個(gè)中間件的最底層第三層的哪個(gè)函數(shù)組成的圓環(huán)函數(shù)構(gòu)成的這就是對(duì)源碼的一個(gè)整體解讀,水平有限,歡迎拍磚。后續(xù)的源碼解讀和測(cè)試?yán)涌梢躁P(guān)注源碼解讀倉(cāng)庫(kù) applyMiddleware源碼解析 中間件機(jī)制在redux中是強(qiáng)大且便捷的,利用redux的中間件我們能夠?qū)崿F(xiàn)日志記錄,異步調(diào)用等多種十分實(shí)用的功能。redux的中間件主要是...
摘要:實(shí)現(xiàn)一個(gè)先不考慮中間件,實(shí)現(xiàn)一個(gè)簡(jiǎn)潔的實(shí)現(xiàn)是最主要的一個(gè)了,通過(guò)可以創(chuàng)建一個(gè)用來(lái)存放應(yīng)用中所有的,一個(gè)應(yīng)用只能有一個(gè)。方法是用來(lái)把每一個(gè)用方法包裹一下,因?yàn)榭赡苤皇欠祷匾粋€(gè)具有屬性的對(duì)象,只有用執(zhí)行才有意義。正好可以利用的特性實(shí)現(xiàn)這個(gè)效果。 實(shí)現(xiàn)一個(gè)redux 先不考慮中間件,實(shí)現(xiàn)一個(gè)簡(jiǎn)潔的redux 實(shí)現(xiàn)createStore createStore是redux最主要的一個(gè)API了,...
摘要:的返回值是函數(shù),這個(gè)函數(shù)經(jīng)調(diào)用,傳入?yún)?shù),之后會(huì)在中間件鏈上進(jìn)行傳遞,只要保證每個(gè)中間件的參數(shù)是并且將傳遞給下一個(gè)中間件。 了解了Redux原理之后,我很好奇Redux中間件是怎么運(yùn)作的,于是選了最常用的redux-thunk進(jìn)行源碼分析。 此次分析用的redux-thunk源碼版本是2.2.0,redux源碼版本是3.7.2。并且需要了解Redux原理 redux中間件都是由redu...
閱讀 1064·2019-08-30 15:55
閱讀 3510·2019-08-30 13:10
閱讀 1334·2019-08-29 18:45
閱讀 2417·2019-08-29 16:25
閱讀 2174·2019-08-29 15:13
閱讀 2491·2019-08-29 11:29
閱讀 613·2019-08-26 17:34
閱讀 1557·2019-08-26 13:57