1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Redux + Hooks 工程實(shí)踐

        共 8877字,需瀏覽 18分鐘

         ·

        2021-08-07 10:29

        點(diǎn)擊上方關(guān)注 前端技術(shù)江湖,一起學(xué)習(xí),天天進(jìn)步


        “都 1202 年了怎么還有人在用 Redux”——這大概不少人看到這篇文章的第一反應(yīng)。首先先表明一下,這篇文章并不討論是不是應(yīng)該使用 Redux,這是一個(gè)比較大的話題,應(yīng)該單獨(dú)水一篇。而且社區(qū)已經(jīng)存在許許多多的討論了,你總能從幾篇高贊的文章中找到一些優(yōu)缺點(diǎn)的對(duì)比圖,然后結(jié)合你項(xiàng)目的場(chǎng)景最終作出決定。我們來(lái)隨便舉幾個(gè)團(tuán)隊(duì)使用 Redux 的原因。首先是易懂,Redux 被人吐槽很多的可能是寫(xiě)法繁瑣,但是在繁瑣寫(xiě)法的背后就沒(méi)有那么多黑科技了,非常容易排查問(wèn)題。另外,Redux 本質(zhì)是對(duì)邏輯處理方式提出了標(biāo)準(zhǔn)范式,并且搭配得給到了一組實(shí)踐規(guī)范,有助于保持項(xiàng)目代碼書(shū)寫(xiě)風(fēng)格與組織方式的一致性,這點(diǎn)在多人合作開(kāi)發(fā)的項(xiàng)目里面尤為重要。其他的優(yōu)點(diǎn)就不在此贅述啦。

        這時(shí)候就有同學(xué)可能要問(wèn)了,你講 Redux,那和 hooks 又有啥子關(guān)系呢。眾所周知,在 React 團(tuán)隊(duì)推出 Hooks 這個(gè)概念后不久,Redux 也更新了對(duì)應(yīng)的 API 來(lái)支持。Hooks 的本質(zhì)是對(duì)邏輯的封裝以及邏輯與 UI 代碼的解耦。有了 Hooks 的加持能夠讓我們的 Redux React 項(xiàng)目更加簡(jiǎn)潔、易懂、擴(kuò)展性更強(qiáng)。而且 Hooks API 在 Redux 的最佳實(shí)踐建議中目前是 Level 2 的強(qiáng)烈推薦使用級(jí)別。他擁有更簡(jiǎn)潔的表達(dá)方式,更干凈的 React 節(jié)點(diǎn)數(shù),更友好的 typescript 支持。

        具體 Redux 相關(guān)的 API 怎么用,這里不做介紹,可以直接跳轉(zhuǎn)官方文檔進(jìn)行了解。下面我們會(huì)從一個(gè)應(yīng)用場(chǎng)景來(lái)具體講一講,他們是怎么幫助我們更好地組織代碼的。其中的部分工程級(jí)別代碼來(lái)自于 react-boilerplate 的項(xiàng)目模版,它在動(dòng)態(tài)加載問(wèn)題上提供了不少幫助。

        封裝案例

        在開(kāi)發(fā)大型 React 應(yīng)用的時(shí)候,動(dòng)態(tài)懶加載代碼永遠(yuǎn)是我們項(xiàng)目架構(gòu)中的必選項(xiàng)。代碼的拆分、動(dòng)態(tài)引用等,工程化工具都已經(jīng)幫我們完成了。我們更需要關(guān)注的是,動(dòng)態(tài)引入與解除掛載等操作時(shí)額外要做什么,以及這個(gè)工作如何盡量少的暴露給項(xiàng)目開(kāi)發(fā)者。前面說(shuō)過(guò)了,Hooks 最強(qiáng)大的能力在于邏輯的封裝,這里當(dāng)然也就要借助他的力量了。

        這里我們以 Reducer 作為例子來(lái)講,其他中間件,例如 Saga 等都可以類(lèi)推,如果需要可以后續(xù)再把相應(yīng)的代碼一并貼出來(lái)。我們把整個(gè)封裝分為三層:核心實(shí)現(xiàn)、可組合封裝、對(duì)開(kāi)發(fā)者暴露封裝。下面我們按順序一一講解。(具體實(shí)現(xiàn)中我都會(huì)默認(rèn)帶上包含 connected router 的實(shí)現(xiàn),方便需要抄代碼的可以直接用)

        核心實(shí)現(xiàn)

        這里的代碼實(shí)現(xiàn)的是如何為一個(gè) store 掛載與解除掛載拆分后的各個(gè) Reducer 的邏輯。

        // 本段代碼完全來(lái)自于 react-boilerplate 項(xiàng)目
        import { combineReducers } from 'redux';
        import { connectRouter } from 'connected-react-router';
        import invariant from 'invariant';
        import { isEmpty, isFunction, isString } from 'lodash';

        import history from '@/utils/history';
        import checkStore from './checkStore'// 做類(lèi)型安全檢測(cè)的,不用關(guān)心

        function createReducer(injectedReducers = {}{
          return history => combineReducers({
            router: connectRouter(history),
            ...injectedReducers,
          });
        }

        export function injectReducerFactory(store, isValid{
          return function injectReducer(key, reducer{
            if (!isValid) checkStore(store);

            invariant(
              isString(key) && !isEmpty(key) && isFunction(reducer),
              '(src/utils...) injectReducer: Expected `reducer` to be a reducer function',
            );

            if (
              Reflect.has(store.injectedReducers, key)
              && store.injectedReducers[key] === reducer
            ) return;

            store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign
            store.replaceReducer(createReducer(store.injectedReducers)(history));
          };
        }

        export default function getInjectors(store{
          checkStore(store);

          return {
            injectReducer: injectReducerFactory(store, true),
          };
        }

        這段有個(gè)點(diǎn)比較特殊,需要講一下。你可能會(huì)發(fā)現(xiàn),這里面根本沒(méi)有解除掛載的部分。這是因?yàn)?nbsp;reducer 比較特殊,他并不會(huì)產(chǎn)生副作用,并且因?yàn)槟壳疤峁┑姆椒ㄊ峭ㄟ^(guò)整個(gè)替換的方式去掛載新的 Reducer,所以并沒(méi)有什么必要去單獨(dú)做解除掛載。在處理其他中間件的掛載時(shí),特別是那些存在副作用的(例如 redux-saga),我們需要對(duì)應(yīng)地實(shí)現(xiàn)一個(gè)解除掛載的 eject 方法。

        OK,那么現(xiàn)在我們已經(jīng)可以通過(guò) getInjectors 方法為整個(gè)項(xiàng)目提供一個(gè) injectReducer 注入 Reducer 的能力了(同時(shí)可能包含 eject 方法)。下一步就是怎么調(diào)度這個(gè)能力。

        可組合的封裝

        這里,我們希望通過(guò)一個(gè)自定義的 hooks,可以允許開(kāi)發(fā)者為一個(gè)組件聲明某一個(gè) 命名空間 的 reducer 與其生命周期一致地進(jìn)行掛載與解除掛載。開(kāi)發(fā)者只需要傳入 reducer 的命名空間與 reducer 實(shí)現(xiàn),并將這個(gè) hooks 放到相應(yīng)的組件邏輯中即可。

        import React from 'react';
        import { ReactReduxContext } from 'react-redux';

        // 這是我們?cè)谏弦徊綄?shí)現(xiàn)的 injector 工廠,通過(guò)他來(lái)產(chǎn)出一個(gè)與固定 store 綁定的 injectReducer 函數(shù)
        import getInjectors from './reducerInjectors';

        const useInjectReducer = ({ key, reducer }) => {
          // 需要從 Redux 的 context 中獲取到當(dāng)前應(yīng)用的全局 store 實(shí)例
          const context = React.useContext(ReactReduxContext);

          // 為了模擬 constructor 的運(yùn)行時(shí)機(jī)
          const initFlagRef = React.useRef(false);
          if (!initFlagRef.current) {
            initFlagRef.current = true;
            getInjectors(context.store).injectReducer(key, reducer);
          }

          // 如果需要加入 eject 的邏輯,則可以使用這樣的寫(xiě)法。類(lèi)似于為當(dāng)前組件增加一個(gè) willUnmount 的生命周期邏輯。
          // React.useEffect(() => (() => {
          //   const injectors = getInjectors(context.store);
          //   injectors.ejectReducer(key);
          // }), []);
        };

        export { useInjectReducer };

        useInjectReducer 這個(gè) Hooks 幫助我們處理了何時(shí)去掛載,怎么掛載等問(wèn)題,我們最終只需要告訴他 掛載什么 就可以了。通過(guò)這層封裝,可以發(fā)現(xiàn)我們進(jìn)一步收斂了關(guān)注點(diǎn)。到這一步為止,我們都是提供了一個(gè)項(xiàng)目級(jí)別的公共方法。在下一步中,我們會(huì)提供一個(gè)統(tǒng)一的寫(xiě)法,在具體的開(kāi)發(fā)過(guò)程中去使用,進(jìn)一步做封裝收斂。

        在進(jìn)入下一步之前,我們先簡(jiǎn)單解釋一下上面的邏輯。邏輯通過(guò)注釋分為了三段(第三段在 reducer 場(chǎng)景下沒(méi)用到),第一段我們通過(guò)當(dāng)前組件所處的 redux 上下文,拿到了 store 的引用,第二段與第三段我們分別讓組件在 初始化 和 銷(xiāo)毀前 執(zhí)行掛載與解除掛載的操作。通過(guò)一個(gè) initFlagRef 為 functional 的組件模擬構(gòu)造器的生命周期(如果有更好的實(shí)現(xiàn)方案歡迎指教),因?yàn)槿绻趻燧d之后再 inject 的話,會(huì)在第一次渲染時(shí)取不到對(duì)應(yīng) store 的內(nèi)容。

        對(duì)開(kāi)發(fā)者暴露封裝

        在完成公用方法的封裝之后,我們下一步考慮的就是如何用更簡(jiǎn)單的方式,為我們的模塊掛載 store 。按照下面的方式,開(kāi)發(fā)者不用關(guān)心任何東西,只需一句話就可以完成掛載,也不用提供額外的參數(shù)。如果同時(shí)有 reducer、saga 或其他中間件內(nèi)容,也可以一起打包搞定。

        import { 
          useInjectReducer, 
          // useInjectSaga,
        from '@/utils/store';

        import actions from './actions';
        import constants from './constants';
        import reducer from './reducer';
        // import saga from './saga';

        const namespace = constants.namespace;

        const useSubStore = () => {
          useInjectReducer({ key: namespace, reducer });
          // useInjectSaga({ key: namespace, saga });
        };

        export {
          namespace,
          actions,
          constants,
          useSubStore,
        };

        實(shí)際使用范例:

        import React from 'react';
        import {
          useSubStore,
        from './store';

        export default function Page({
          useSubStore();

          return <div />;
        };

        具體的數(shù)據(jù)和邏輯我們也可以封裝成幾個(gè) Hooks ,例如我們需要提供一個(gè)數(shù)組數(shù)據(jù)簡(jiǎn)單操作,我們只關(guān)心 添加 和 數(shù)量,就可以封裝一個(gè) Hooks,這樣實(shí)際使用方只需要關(guān)心 添加 和 數(shù)量 這兩個(gè)要素,不用關(guān)心 redux 的具體實(shí)現(xiàn)方式了。

        import { useMemo, useCallback } from 'react';
        import { useDispatch, useSelector } from 'react-redux';

        import {
          actions, constants, namespace,
        from './store';

        export function useItemList({
          const dispatch = useDispatch();
          const list = useSelector(state => state[namespace].itemList);
          // 這只是范例!
          const count = useMemo(() => list.length, [list]);
          const add = useCallback((item) => dispatch(actions.addItem(item)), []);

          return [count, add];
        }

        下面我們修改一下使用的地方:

        import React from 'react';
        import {
          useSubStore,
        from './store';
        import { useItemList } from './useItemList';

        export default function Page({
          useSubStore();
          const [count, add] = useItemList();

          return <div onClick={() => add({})}>{count}</div>;
        };

        通過(guò)這樣一種拆分方式,store 的定義,store 的使用邏輯,業(yè)務(wù)側(cè)三者都只關(guān)注自己必須關(guān)注的部分,任何一方改動(dòng)都可以盡量少地引起變更。

        可復(fù)用的 Hooks

        那我們進(jìn)一步思考一下,以前我們可能一個(gè)頁(yè)面對(duì)應(yīng)一個(gè) store。通過(guò) Hooks 進(jìn)行拆分后,我們更方便從功能層面去拆分 store,store 的邏輯也會(huì)更為清晰。與 store 的交互被封裝成了 Hooks 之后也可以很快在多個(gè)展示層被使用。這在復(fù)雜 B 端工作臺(tái)場(chǎng)景下會(huì)展現(xiàn)出很大的價(jià)值。案例會(huì)有點(diǎn)長(zhǎng),以后有時(shí)間可以再補(bǔ)上。

        回顧

        看完上面的例子,相信聰明的讀者已經(jīng)知道我想表達(dá)的問(wèn)題了。通過(guò)結(jié)合 Redux + Hooks,標(biāo)準(zhǔn)化了定義代碼,對(duì)邏輯、調(diào)用、定義三者一定程度上進(jìn)行了解耦。通過(guò)簡(jiǎn)化的 API,減少了邏輯的理解成本,減少了后續(xù)維護(hù)的復(fù)雜度,一定程度上還可以達(dá)到復(fù)用。不管是相較于過(guò)去的 Redux 接入方案,還是相較于單純使用 Hooks,都有著其獨(dú)特的優(yōu)勢(shì)。特別適用于邏輯相對(duì)復(fù)雜的工作臺(tái)場(chǎng)景。(而且我很喜歡 Saga的設(shè)計(jì)思路,能用起來(lái)就很爽)。

        OK,收。這次以一個(gè)簡(jiǎn)單的例子,稍稍展示了一下在 Hooks 大環(huán)境下 Redux 與其產(chǎn)生的化學(xué)反應(yīng)。主要想展示的是依賴(lài) Hooks 的邏輯可封裝能力的一種設(shè)計(jì)思路,Redux 黑的同學(xué)們不要過(guò)多糾結(jié)與這個(gè)選型,蘿卜青菜各有所愛(ài)。

        希望這個(gè)系列能繼續(xù)寫(xiě)下去 :)

        作者:ES2049 / armslave00 https://zhuanlan.zhihu.com/p/374788504 非常歡迎有激情的你加入 ES2049 Studio,簡(jiǎn)歷請(qǐng)發(fā)送至 [email protected] 。

        The End

        歡迎自薦投稿到《前端技術(shù)江湖》,如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),記得點(diǎn)個(gè) 「在看」


        點(diǎn)個(gè)『在看』支持下 


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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            永久成人无码激情视频免费 | 穴穴自拍九九综合 | 特级婬片AAAAAAA级附近的 | 三级黄色小视频 | 日韩一区二区视频在线 | 久久夜色精品国产欧美乱极品 | 亚洲天堂2021av | 人人超逼逼 | 8 8 海外华人免费一区 | 情趣视频网站在线观看成人免费 |