<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redux通關(guān)簡(jiǎn)潔攻略 -- 看這一篇就夠了!

          共 8046字,需瀏覽 17分鐘

           ·

          2021-10-24 03:11

          大廠技術(shù)??堅(jiān)持周更??精選好文

          「Content」:本文章簡(jiǎn)要分析Redux & Redux生態(tài)的原理及其實(shí)現(xiàn)方式。
          「Require」:理解本文需要一定redux的使用經(jīng)驗(yàn)。
          「Gain」:將收獲

          1. 再寫Redux,清楚地知道自己在做什么,每行代碼會(huì)產(chǎn)生什么影響。
          2. 理解storeEnhancer middleware的工作原理,根據(jù)需求可以自己創(chuàng)造。
          3. 學(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ī)則:

          1. 處理未定義type的action,直接返回入?yún)⒌膕tate。
          2. 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做了兩件事 :

          1. 調(diào)用reducer 產(chǎn)生新的state。
          2. 調(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)

          1. 通過(guò)React Context存儲(chǔ)全局狀態(tài)
          export?const?ReactReduxContext?=???/*#__PURE__*/?
          React.createContextnull>(null)?
          1. 把它封裝成Provider組件
          function?Provider({?store,?context,?children?}:?ProviderProps)?{?????
          ????const?Context?=?context?||?ReactReduxContext?????
          ????return?<Context.Provider?value={contextValue}>{children}Context.Provider>??
          }?
          1. 提供獲取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.

          1. subscribe
          ??const?subscription?=?useMemo(
          ??()?=>?createSubscription(store),?????
          ??[store,?contextSub]
          ??)???
          ??subscription.onStateChange?=?checkForUpdates?
          1. 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()????
          ????}?
          1. 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?收貨大廠一手好文章~


          瀏覽 35
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品秘 麻豆 | 大大香蕉久久 | 永久免费无码中文字幕 | 国产色拍 | 人体艺术香蕉视频 |