Redux通關(guān)簡(jiǎn)潔攻略 -- 看這一篇就夠了!
大廠技術(shù)??堅(jiān)持周更??精選好文
「Content」:本文章簡(jiǎn)要分析Redux & Redux生態(tài)的原理及其實(shí)現(xiàn)方式。
「Require」:理解本文需要一定redux的使用經(jīng)驗(yàn)。
「Gain」:將收獲
再寫Redux,清楚地知道自己在做什么,每行代碼會(huì)產(chǎn)生什么影響。 理解storeEnhancer middleware的工作原理,根據(jù)需求可以自己創(chuàng)造。 學(xué)習(xí)函數(shù)式范式是如何在實(shí)踐中應(yīng)用的大量?jī)?yōu)秀示例。 「Correction」:如有寫錯(cuò)的地方,歡迎評(píng)論反饋
Redux設(shè)計(jì)哲學(xué)
Single source of truth
只能存在一個(gè)唯一的全局?jǐn)?shù)據(jù)源,狀態(tài)和視圖是一一對(duì)應(yīng)關(guān)系

Data - View Mapping
State is read-only
狀態(tài)是只讀的,當(dāng)我們需要變更它的時(shí)候,用一個(gè)新的來(lái)替換,而不是在直接在原數(shù)據(jù)上做更改。
Changes are made with pure functions
狀態(tài)更新通過(guò)一個(gè)純函數(shù)(Reducer)完成,它接受一個(gè)描述狀態(tài)如何變化的對(duì)象(Action)來(lái)生成全新的狀態(tài)。
?State Change
????純函數(shù)的特點(diǎn)是函數(shù)輸出不依賴于任何外部變量,相同的輸入一定會(huì)產(chǎn)生相同的輸出,非常穩(wěn)定。使用它來(lái)進(jìn)行全局狀態(tài)修改,使得全局狀態(tài)可以被預(yù)測(cè)。當(dāng)前的狀態(tài)決定于兩點(diǎn):1. 初始狀態(tài) 2. 狀態(tài)存在期間傳遞的Action序列,只要記錄這兩個(gè)要素,就可以還原任何一個(gè)時(shí)間點(diǎn)的狀態(tài),實(shí)現(xiàn)所謂的“時(shí)間旅行”(Redux DevTools)

Single State + Pure Function
Redux架構(gòu)
Redux組件
state: 全局的狀態(tài)對(duì)象,唯一且不可變。 store: 調(diào)用createStore 函數(shù)生成的對(duì)象,里面封入了定義在createStore內(nèi)部用于操作全局狀態(tài)的方法,用戶通過(guò)這些方法使用Redux。 action: 描述狀態(tài)如何修改的對(duì)象,固定擁有一個(gè)type屬性,通過(guò)store的dispatch方法提交。 reducer: 實(shí)際執(zhí)行狀態(tài)修改的純函數(shù),由用戶定義并傳入,接收來(lái)自dispatch的
action作為參數(shù),計(jì)算返回全新的狀態(tài),完成state的更新,然后執(zhí)行訂閱的監(jiān)聽函數(shù)。
storeEnhancer: createStore的高階函數(shù)封裝,用于加強(qiáng)store的能力,redux提供的applyMiddleware是官方實(shí)現(xiàn)的一個(gè)storeEnhancer。 middleware: dispatch的高階函數(shù)封裝,由applyMiddleware把原dispatch替換為包含middleware鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn)。
Redux構(gòu)成

Redux API 實(shí)現(xiàn)
Redux Core
createStore
????createStore 是一個(gè)大的閉包環(huán)境,里面定義了store本身,以及store的各種api。環(huán)境內(nèi)部有對(duì)如獲取state 、觸發(fā)dispatch 、改動(dòng)監(jiān)聽等副作用操作做檢測(cè)的標(biāo)志,因此reducer 被嚴(yán)格控制為純函數(shù)。
????redux設(shè)計(jì)的所有核心思想都在這里面實(shí)現(xiàn),整個(gè)文件只有三百多行,簡(jiǎn)單但重要,下面簡(jiǎn)要列出了這個(gè)閉包中實(shí)現(xiàn)的功能及源碼解析,以加強(qiáng)理解。
如果有storeEnhancer,則應(yīng)用storeEnhancer
if?(typeof?enhancer?!==?'undefined')?{
//?類型檢測(cè)
if?(typeof?enhancer?!==?'function')?{
...
}
//?enhancer接受一個(gè)storeCreator返回一個(gè)storeCreator
//?在應(yīng)用它的時(shí)候直接把它返回的storeCreatetor執(zhí)行了然后返回對(duì)應(yīng)的store
return?enhancer(createStore)(reducer,preloadedState)
}
????否則dispatch一個(gè)INIT的action,目的是讓reducer產(chǎn)生一個(gè)初始的state。注意這里的INIT是Redux內(nèi)部定義的隨機(jī)數(shù),reducer無(wú)法對(duì)它有明確定義的處理,而且此時(shí)的state可能為undefined,故為了能夠順利完成初始化,編寫reducer時(shí)候我們需要遵循下面的兩點(diǎn)規(guī)則:
處理未定義type的action,直接返回入?yún)⒌膕tate。 createStore如沒(méi)有傳入初始的state,則reducer中必須提供默認(rèn)值。
//?When?a?store?is?created,?an?"INIT"?action?is?dispatched?so?that?every
//?reducer?returns?their?initial?state.?This?effectively?populates
//?the?initial?state?tree.
dispatch({?type:?ActionTypes.INIT?}?as?A)
最后把閉包內(nèi)定義的方法裝入store對(duì)象并返回
const?store?=?{
dispatch,
subscribe,
getState,
replaceReducer,?//?不常用,故略過(guò)
[$$observable]:?observable?//?不常用,故略過(guò)
}
return?store;
下面是這些方法的實(shí)現(xiàn)方式
getState
規(guī)定不能在reducer里調(diào)用getState,符合條件就返回當(dāng)前狀態(tài),很清晰,不再贅述。
function?getState(){
if?(isDispatching)?{
????...
}
return?currentState
}
dispatch
內(nèi)置的dispatch 只提供了普通對(duì)象Action 的支持,其余像AsyncAction 的支持放到了middleware 中。dispatch做了兩件事 :
調(diào)用reducer 產(chǎn)生新的state。 調(diào)用訂閱的監(jiān)聽函數(shù)。
/*
*?通過(guò)原型鏈判斷是否是普通對(duì)象
對(duì)于一個(gè)普通對(duì)象,它的原型是Object
*/
function?isPlainObject(obj){
????if?(typeof?obj?!==?'object'?||?obj?===?null)?return?false
????let?proto?=?obj
????//?proto出循環(huán)后就是Object
????while?(Object.getPrototypeOf(proto)?!==?null)?{
????????proto?=?Object.getPrototypeOf(proto)
????}
????return?Object.getPrototypeOf(obj)?===?proto
}
function?dispatch(action:?A)?{
????//?判斷一下是否是普通對(duì)象
????if?(!isPlainObject(action))?{
????????...
????}
????//?redux要求action中需要有個(gè)type屬性
????if?(typeof?action.type?===?'undefined')?{
????????...
????}
????//?reducer中不允許使用
????if?(isDispatching)?{
????????...
????}
????//?調(diào)用reducer產(chǎn)生新的state?然后替換掉當(dāng)前的state
????try?{
????????isDispatching?=?true
????????currentState?=?currentReducer(currentState,?action)
????}?finally?{
????????isDispatching?=?false
????}
????//?調(diào)用訂閱的監(jiān)聽
????const?listeners?=?(currentListeners?=?nextListeners)
????for?(let?i?=?0;?i?????????const?listener?=?listeners[i]
????????listener()
????}
????return?action
}
subscribe
訂閱狀態(tài)更新,并返回取消訂閱的方法。實(shí)際上只要發(fā)生dispatch調(diào)用,就算reducer 不對(duì)state做任何改動(dòng),監(jiān)聽函數(shù)也一樣會(huì)被觸發(fā),所以為了減少渲染,各個(gè)UI bindings中會(huì)在自己注冊(cè)的listener中做 state diff來(lái)優(yōu)化性能。注意listener 是允許副作用存在的。
//?把nextListeners做成currentListeners的一個(gè)切片,之后對(duì)切片做修改,替換掉currentListeners
function?ensureCanMutateNextListeners()?{
????if?(nextListeners?===?currentListeners)?{
????????nextListeners?=?currentListeners.slice()
????}
}
function?subscribe(listener:?()?=>?void)?{
????//?類型檢測(cè)
????if(typeof?listener?!==?'function'){
????????...
????}
????//?reducer?中不允許訂閱
????if?(isDispatching)?{
????????...
????}
????let?isSubscribed?=?true
????ensureCanMutateNextListeners()
????nextListeners.push(listener)
????return?function?unsubscribe()?{
????//?防止重復(fù)取消訂閱
????if?(!isSubscribed)?{
????????return
????}
????//?reducer中也不允許取消訂閱
????if?(isDispatching)?{
????????...
????}
????isSubscribed?=?false
????ensureCanMutateNextListeners()
????const?index?=?nextListeners.indexOf(listener)
????nextListeners.splice(index,?1)
????currentListeners?=?null
????}
}
applyMiddleware
applyMiddleware 是官方實(shí)現(xiàn)的一個(gè)storeEnhance,用于給redux提供插件能力,支持各種不同的Action。
storeEnhancer
從函數(shù)簽名可以看出是createStore的高階函數(shù)封裝。
type?StoreEnhancer?=?(next:?StoreCreator)?=>?StoreCreator;?
CreateStore 入?yún)⒅兄唤邮芤粋€(gè)storeEnhancer ,如果需要傳入多個(gè),則用compose把他們組合起來(lái),關(guān)于高階函數(shù)組合的執(zhí)行方式下文中的Redux Utils - compose有說(shuō)明,這對(duì)理解下面middleware 是如何鏈?zhǔn)秸{(diào)用的至關(guān)重要,故請(qǐng)先看那一部分。
middleware
type?MiddlewareAPI?=?{?dispatch:?Dispatch,?getState:?()?=>?State?}?
type?Middleware?=?(api:?MiddlewareAPI)?=>?(next:?Dispatch)?=>?Dispatch?
最外層函數(shù)的作用是接收middlewareApi ,給middleware提供store 的部分api,它返回的函數(shù)參與compose,以實(shí)現(xiàn)middleware的鏈?zhǔn)秸{(diào)用。
export?default?function?applyMiddleware(...middlewares)?{
????return?(createStore)?=>{?????????????
????????????//?初始化store,拿到dispatch?????????????
????????????const?store?=?createStore(reducer,?preloadedState)?????????????
????????????//?不允許在middlware中調(diào)用dispath?????????????
????????????let?dispatch:?Dispatch?=?()?=>?{?????????????????
????????????????throw?new?Error(?????????????????????
????????????????'Dispatching?while?constructing?your?middleware?is?not?allowed.?'?+?????????????????????'Other?middleware?would?not?be?applied?to?this?dispatch.'?????????????????
????????????????)?????????????
????????????}?????????????
????????????const?middlewareAPI:?MiddlewareAPI?=?{
????????????????getState:?store.getState,?????????????????
????????????????dispatch:?(action,?...args)?=>?dispatch(action,?...args)
????????????}????????????
????????????//?把a(bǔ)pi注入middlware?????????????
????????????const?chain?=?middlewares.map(middleware?=>?middleware(middlewareAPI))?????????????????//?重點(diǎn)理解
????????????//?compose后傳入dispatch,生成一個(gè)新的經(jīng)過(guò)層層包裝的dispath調(diào)用鏈
????????????dispatch?=?compose<typeof?dispatch>(...chain)(store.dispatch)
????????????//?替換掉dispath,返回
????????????return?{?????????????????
????????????????...store,?????????????????
????????????????dispatch?????????????
????????????}?????????
????????}?
}?
再來(lái)看一個(gè)middleware加深理解:redux-thunk 使 redux 支持asyncAction ,它經(jīng)常被用于一些異步的場(chǎng)景中。
//?最外層是一個(gè)中間件的工廠函數(shù),生成middleware,并向asyncAction中注入額外參數(shù)??
function?createThunkMiddleware(extraArgument)?{???
????return?({?dispatch,?getState?})?=>?(next)?=>?
????????(action)?=>?{?????
????????//?在中間件里判斷action類型,如果是函數(shù)那么直接執(zhí)行,鏈?zhǔn)秸{(diào)用在這里中斷
????????if?(typeof?action?===?'function')?{???????
????????????return?action(dispatch,?getState,?extraArgument);?????}?????//?否則繼續(xù)?????
????????????return?next(action);???
????};
}?
Redux Utils
compose
????compose(組合)是函數(shù)式編程范式中經(jīng)常用到的一種處理,它創(chuàng)建一個(gè)從右到左的數(shù)據(jù)流,右邊函數(shù)執(zhí)行的結(jié)果作為參數(shù)傳入左邊。
????compose是一個(gè)高階函數(shù),接受n個(gè)函數(shù)參數(shù),返回一個(gè)以上述數(shù)據(jù)流執(zhí)行的函數(shù)。如果參數(shù)數(shù)組也是高階函數(shù),那么它c(diǎn)ompose后的函數(shù)最終執(zhí)行過(guò)程就變成了如下圖所示,高階函數(shù)數(shù)組返回的函數(shù)將是一個(gè)從左到右鏈?zhǔn)秸{(diào)用的過(guò)程。
export?default?function?compose(...funcs)?{
????if?(funcs.length?===?0)?{
????????return?(arg)?=>?arg
????}
????if?(funcs.length?===?1)?{
????????return?funcs[0]
????}
????//?簡(jiǎn)單直接的compose
????return?funcs.reduce(
????????(a,?b)?=>
????????????(...args:?any)?=>
????????????????a(b(...args))
????)
}
combineReducers
它也是一種組合,但是是樹狀的組合??梢詣?chuàng)建復(fù)雜的Reducer,如下圖
實(shí)現(xiàn)的方法也較為簡(jiǎn)單,就是把map對(duì)象用函數(shù)包一層,返回一個(gè)mapedReducer,下面是一個(gè)簡(jiǎn)化的實(shí)現(xiàn)。
function?combineReducers(reducers){??????
????const?reducerKeys?=?Object.keys(reducers)??????
????const?finalReducers?=?{}??????
????for?(let?i?=?0;?i?????????const?key?=?reducerKeys[i]??????????
????????finalReducers[key]?=?reducers[key]
????}?????
????const?finalReducerKeys?=?Object.keys(finalReducers)?????
????//?組合后的reducer?????
????return?function?combination(state,?action){?????????
????????let?hasChanged?=?false?????????
????????const?nextState?=?{}?????????
????????//?遍歷然后執(zhí)行?????????
????????for?(let?i?=?0;?i?????????????const?key?=?finalReducerKeys[i]???????????
????????????const?reducer?=?finalReducers[key]???????????
????????????const?previousStateForKey?=?state[key]???????????
????????????const?nextStateForKey?=?reducer(previousStateForKey,?action)???????????
????????????if?(typeof?nextStateForKey?===?'undefined')?{
????????????...???????????
????????????}???????????
????????????nextState[key]?=?nextStateForKey???????????
????????????hasChanged?=?hasChanged?||?nextStateForKey?!==?previousStateForKey
????????}?????????
????????hasChanged?=?hasChanged?||?finalReducerKeys.length?!==?Object.keys(state).length?????????
????????return?hasChanged???nextState?:?state?????????
????????}?????
????}?
}?
bindActionCreators
用actionCreator創(chuàng)建一個(gè)Action,立即dispatch它
function?bindActionCreator(actionCreator,dispatch)?{???
????return?function?(this,?...args)?{?????
????????return?dispatch(actionCreator.apply(this,?args))
????}
}?
Redux UI bindings
React-redux
????React-redux 是Redux官方實(shí)現(xiàn)的React UI bindings。它提供了兩種使用Redux的方式:HOC和Hooks,分別對(duì)應(yīng)Class組件和函數(shù)式組件。我們選擇它的Hooks實(shí)現(xiàn)來(lái)分析,重點(diǎn)關(guān)注UI組件是如何獲取到全局狀態(tài)的,以及當(dāng)全局狀態(tài)變更時(shí)如何通知UI更新。
UI如何獲取到全局狀態(tài)
通過(guò)React Context存儲(chǔ)全局狀態(tài)
export?const?ReactReduxContext?=???/*#__PURE__*/?
React.createContextnull>(null)?
把它封裝成Provider組件
function?Provider({?store,?context,?children?}:?ProviderProps)?{?????
????const?Context?=?context?||?ReactReduxContext?????
????return?<Context.Provider?value={contextValue}>{children}Context.Provider>??
}?
提供獲取store的 hook: useStore
function?useStore(){?????
????const?{?store?}?=?useReduxContext()!??????
????return?store?
}?
State變更時(shí)如何通知UI更新
react-redux提供了一個(gè)hook:useSelector,這個(gè)hook向redux subscribe了一個(gè)listener,當(dāng)狀態(tài)變化時(shí)被觸發(fā)。它主要做下面幾件事情。
When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render.
subscribe
??const?subscription?=?useMemo(
??()?=>?createSubscription(store),?????
??[store,?contextSub]
??)???
??subscription.onStateChange?=?checkForUpdates?
state diff
????function?checkForUpdates()?{???????
????????try?{?????????
????????????const?newStoreState?=?store.getState()?????????
????????????const?newSelectedState?=?latestSelector.current!(newStoreState)??????????
????????????if?(equalityFn(newSelectedState,?latestSelectedState.current))?{???????????
????????????????return?????????
????????????}??????????
????????????latestSelectedState.current?=?newSelectedState?????????
????????????latestStoreState.current?=?newStoreState???????
????????}?catch?(err)?{????????
????????????//?we?ignore?all?errors?here,?since?when?the?component
????????????//?is?re-rendered,?the?selectors?are?called?again,?and
????????????//?will?throw?again,?if?neither?props?nor?store?state
????????????//?changed?????????
????????????latestSubscriptionCallbackError.current?=?err?as?Error???????
????????}????????
????????forceRender()????
????}?
re-render
?const?[,?forceRender]?=?useReducer((s)?=>?s?+?1,?0)??
?forceRender()?
脫離UI bindings,如何使用redux
其實(shí)只要完成上面三個(gè)步驟就能使用,下面是一個(gè)示例:
const?App?=?()=>{?????
const?state?=?store.getState();?????
const?[,?forceRender]?=?useReducer(c=>c+1,?0);??????
//?訂閱更新,狀態(tài)變更刷新組件?????
useEffect(()=>{?????????
????//?組件銷毀時(shí)取消訂閱?????????
????return?store.subscribe(()=>{?????????????
????forceRender();?????????
????});????
},[]);??????
const?onIncrement?=?()=>?{?????????
????store.dispatch({type:?'increment'});?????
};?????
const?onDecrement?=?()=>?{?????????
????store.dispatch({type:?'decrement'});?????
}?????????
return?(
<div?style={{textAlign:'center',?marginTop:'35%'}}>
????<h1?style={{color:?'green',?fontSize:?'500%'}}>{state.count}h1>
????<button?onClick={onDecrement}?style={{marginRight:?'10%'}}>decrementbutton>
????<button?onClick={onIncrement}>incrementbutton>?????????
div>?????????
)?
}
小結(jié)
????Redux核心部分單純實(shí)現(xiàn)了它“單一狀態(tài)”、“狀態(tài)不可變”、“純函數(shù)”的設(shè)定,非常小巧。對(duì)外暴露出storeEnhancer 與 middleware以在此概念上添加功能,豐富生態(tài)。redux的發(fā)展也證明這樣的設(shè)計(jì)思路使redux拓展性非常強(qiáng)。
????其中關(guān)于高階函數(shù)的應(yīng)用是我覺(jué)得非常值得借鑒的一個(gè)插件體系構(gòu)建方式,不是直接設(shè)定生命周期,而是直接給予核心函數(shù)一次高階封裝,然后內(nèi)部依賴compose完成鏈?zhǔn)秸{(diào)用,這可以降低外部開發(fā)者的開發(fā)心智。
????Redux想要解決的問(wèn)題是復(fù)雜狀態(tài)與視圖的映射難題,但Redux本身卻沒(méi)有直接實(shí)現(xiàn),它只做了狀態(tài)管理,然后把狀態(tài)更新的監(jiān)聽能力暴露出去,剩下的狀態(tài)緩存、狀態(tài)對(duì)比、更新視圖就拋給各大框架的UI-bindings,這既在保持了自身代碼的單一性、穩(wěn)定性、又能給真正在視圖層使用redux狀態(tài)管理的開發(fā)者提供“類響應(yīng)式”的State-View開發(fā)體驗(yàn)。
???謝謝支持
以上便是本次分享的全部?jī)?nèi)容,希望對(duì)你有所幫助^_^
喜歡的話別忘了?分享、點(diǎn)贊、收藏?三連哦~。
歡迎關(guān)注公眾號(hào) 前端Sharing?收貨大廠一手好文章~
