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>

        ssh 在大廠寫React,學(xué)到了什么?

        共 5738字,需瀏覽 12分鐘

         ·

        2020-10-28 22:21

        前言

        進(jìn)入大廠搬磚也有 3 個(gè)月了,我工作中的技術(shù)棧主要是 React + TypeScript,這篇文章我想總結(jié)一下如何在項(xiàng)目中運(yùn)用 React 的一些技巧解決一些實(shí)際問題,本文中使用的代碼都是簡化后的,不代表生產(chǎn)環(huán)境。生產(chǎn)環(huán)境的代碼肯定比文中的例子要復(fù)雜很多,但是簡化后的思想應(yīng)該是相通的。

        取消請(qǐng)求

        React 中當(dāng)前正在發(fā)出請(qǐng)求的組件從頁面上卸載了,理想情況下這個(gè)請(qǐng)求也應(yīng)該取消掉,那么如何把請(qǐng)求的取消和頁面的卸載關(guān)聯(lián)在一起呢?

        這里要考慮利用 useEffect 傳入函數(shù)的返回值:

        useEffect(()?=>?{
        ??return?()?=>?{
        ????//?頁面卸載時(shí)執(zhí)行
        ??};
        },?[]);

        假設(shè)我們的請(qǐng)求是利用 fetch,那么還有一個(gè)需要運(yùn)用的知識(shí)點(diǎn):AbortController,簡單看一下它的用法:

        const?abortController?=?new?AbortController();

        fetch(url,?{
        ??//?這里傳入?signal?進(jìn)行關(guān)聯(lián)
        ??signal:?abortController.signal,
        });

        //?這里調(diào)用?abort?即可取消請(qǐng)求
        abortController.abort();

        那么結(jié)合 React 封裝一個(gè) useFetch 的 hook:

        export?function?useFetch?=?(config,?deps)?=>?{
        ??const?abortController?=?new?AbortController()
        ??const?[loading,?setLoading]?=?useState(false)
        ??const?[result,?setResult]?=?useState()

        ??useEffect(()?=>?{
        ????setLoading(true)
        ????fetch({
        ??????...config,
        ??????signal:?abortController.signal
        ????})
        ??????.then((res)?=>?setResult(res))
        ??????.finally(()?=>?setLoading(false))
        ??},?deps)

        ??useEffect(()?=>?{
        ????return?()?=>?abortController.abort()
        ??},?[])

        ??return?{?result,?loading?}
        }

        那么比如在路由發(fā)生切換,Tab 發(fā)生切換等場景下,被卸載掉的組件發(fā)出的請(qǐng)求也會(huì)被中斷。

        深比較依賴

        在使用 useEffect 等需要傳入依賴的 hook 時(shí),最理想的狀況是所有依賴都在真正發(fā)生變化的時(shí)候才去改變自身的引用地址,但是有些依賴不太聽話,每次渲染都會(huì)重新生成一個(gè)引用,但是內(nèi)部的值卻沒變,這可能會(huì)讓 useEffect 對(duì)于依賴的「淺比較」沒法正常工作。

        比如說:

        const?getDep?=?()?=>?{
        ??return?{
        ????foo:?'bar',
        ??};
        };

        useEffect(()?=>?{
        ??//?無限循環(huán)了
        },?[getDep()]);

        這是一個(gè)人為的例子,由于 getDeps 函數(shù)返回的對(duì)象每次執(zhí)行都是一個(gè)全新的引用,所以會(huì)導(dǎo)致觸發(fā)渲染->effect->渲染->effect 的無限更新。

        有一個(gè)比較取巧的解決辦法,把依賴轉(zhuǎn)為字符串:

        const?getDep?=?()?=>?{
        ??return?{
        ????foo:?'bar',
        ??};
        };

        const?dep?=?JSON.stringify(getDeps());

        useEffect(()?=>?{
        ??//?ok!
        },?[dep]);

        這樣對(duì)比的就是字符串 "{ foo: 'bar' }" 的值,而不是對(duì)象的引用,那么只有在值真正發(fā)生變化時(shí)才會(huì)觸發(fā)更新。

        當(dāng)然最好還是用社區(qū)提供的方案:useDeepCompareEffect,它選用深比較策略,對(duì)于對(duì)象依賴來說,它逐個(gè)對(duì)比 key 和 value,在性能上會(huì)有所犧牲。

        如果你的某個(gè)依賴觸發(fā)了多次無意義的接口請(qǐng)求,那么寧愿選用 useDeepCompareEffect ,在對(duì)象比較上多花費(fèi)些時(shí)間可比重復(fù)請(qǐng)求接口要好得多。

        useDeepCompareEffect 大致原理:

        import?{?isEqual?}?from?'lodash';
        export?function?useDeepCompareEffect(fn,?deps)?{
        ??const?trigger?=?useRef(0);
        ??const?prevDeps?=?useRef(deps);
        ??if?(!isEqual(prevDeps.current,?deps))?{
        ????trigger.current++;
        ??}
        ??prevDeps.current?=?deps;
        ??return?useEffect(fn,?[trigger.current]);
        }

        真正傳入 useEffect 用以更新的是 trigger 這個(gè)數(shù)字值。用useRef 保留上一次傳入的依賴,每次都利用 lodash 的 isEqual 對(duì)本次依賴和舊依賴進(jìn)行深比較,如果發(fā)生變化,則讓 trigger 的值增加。

        當(dāng)然我們也可以用 fast-deep-equal 這個(gè)庫,根據(jù)官方的 benchmark 對(duì)比,它比 lodash 的效率高 7 倍左右。

        以 URL 為數(shù)據(jù)倉庫

        在公司內(nèi)部的后臺(tái)管理項(xiàng)目中,無論你做的系統(tǒng)面向的人群是運(yùn)營還是開發(fā),都會(huì)涉及到分享,那么保留「頁面狀態(tài)」就非常重要了。比如我是運(yùn)營 A,在使用一個(gè)內(nèi)部數(shù)據(jù)平臺(tái),我一定是想向運(yùn)營 B 分享某 App 的消費(fèi)數(shù)據(jù)的第二頁,并且篩選為某個(gè)用戶的狀態(tài)的網(wǎng)頁,并且進(jìn)行討論。那么狀態(tài)和 URL 同步就尤為重要了。

        在傳統(tǒng)的狀態(tài)管理思路中,我們需要在代碼里用redux、recoil等庫去做一系列的數(shù)據(jù)管理,但是如果把 URL 后面的那串 query 想象成數(shù)據(jù)倉庫呢?是不是也可以,嘗試配合react-router封裝一下。

        export?function?useQuery()?{
        ??const?history?=?useHistory();
        ??const?{?search,?pathname?}?=?useLocation();
        ??//?保存query狀態(tài)
        ??const?queryState?=?useRef(qs.parse(search));
        ??//?設(shè)置query
        ??const?setQuery?=?handler?=>?{
        ????const?nextQuery?=?handler(queryState.current);
        ????queryState.current?=?nextQuery;
        ????//?replace會(huì)使組件重新渲染
        ????history.replace({
        ??????pathname:?pathname,
        ??????search:?qs.stringify(nextQuery),
        ????});
        ??};
        ??return?[queryState.current,?setQuery];
        }

        在組件中,可以這樣使用:

        const?[query,?setQuery]?=?useQuery();

        //?接口請(qǐng)求依賴?page?和?size
        useEffect(()?=>?{
        ??api.getUsers();
        },?[query.page,?query,?size]);

        //?分頁改變?觸發(fā)接口重新請(qǐng)求
        const?onPageChange?=?page?=>?{
        ??setQuery(prevQuery?=>?({
        ????...prevQuery,
        ????page,
        ??}));
        };

        這樣,所有的頁面狀態(tài)更改都會(huì)自動(dòng)同步到 URL,非常方便。

        利用 AST 做國際化

        國際化中最頭疼的就是手動(dòng)去替換代碼中的文本,轉(zhuǎn)為 i18n.t(key) 這種國際化方法調(diào)用,而這一步則可以交給 Babel AST 去完成。掃描出代碼中需要替換文本的位置,修改 AST 把它轉(zhuǎn)為方法調(diào)用即可,比較麻煩的點(diǎn)在于需要考慮各種邊界情況,我寫過一個(gè)比較簡單的例子,僅供參考:

        https://github.com/sl1673495/babel-ast-practise/blob/master/i18n.js

        這樣的一段源代碼:

        import?React?from?'react';
        import?{?Button,?Toast,?Popover?}?from?'components';
        const?Comp?=?props?=>?{
        ??const?tips?=?()?=>?{
        ????Toast.info('這是一段提示');
        ????Toast({
        ??????text:?'這是一段提示',
        ????});
        ??};
        ??return?(
        ????<div>
        ??????<Button?onClick={tips}>這是按鈕Button>

        ??????<Popover?tooltip="氣泡提示"?/>
        ????div>
        ??);
        };
        export?default?Comp;

        經(jīng)過處理后,轉(zhuǎn)變?yōu)檫@樣:

        import?React?from?'react';
        import?{?useI18n?}?from?'react-intl';
        import?{?Button,?Toast,?Popover?}?from?'components';
        const?Comp?=?props?=>?{
        ??const?{?t?}?=?useI18n();
        ??const?tips?=?()?=>?{
        ????Toast.info(t('tips'));
        ????Toast({
        ??????text:?t('tips'),
        ????});
        ??};
        ??return?(
        ????<div>
        ??????<Button?onClick={tips}>{t('btn')}Button>

        ??????<Popover?tooltip={t('popover')}?/>
        ????div>
        ??);
        };
        export?default?Comp;

        放一段腳本的 traverse 部分:

        //?遍歷ast
        traverse(ast,?{
        ??Program(path)?{
        ????//?i18n的import導(dǎo)入?一般第一項(xiàng)一定是import?React?所以直接插入在后面就可以
        ????path.get('body.0').insertAfter(makeImportDeclaration(I18_HOOK,?I18_LIB));
        ??},
        ??//?通過找到第一個(gè)jsxElement?來向上尋找Component函數(shù)并且插入i18n的hook函數(shù)
        ??JSXElement(path)?{
        ????const?functionParent?=?path.getFunctionParent();
        ????const?functionBody?=?functionParent.node.body.body;
        ????if?(!this.hasInsertUseI18n)?{
        ??????functionBody.unshift(
        ????????buildDestructFunction({
        ??????????VALUE:?t.identifier(I18_FUNC),
        ??????????SOURCE:?t.callExpression(t.identifier(I18_HOOK),?[]),
        ????????})
        ??????);
        ??????this.hasInsertUseI18n?=?true;
        ????}
        ??},
        ??//?jsx中的文字?直接替換成{t(key)}的形式
        ??JSXText(path)?{
        ????const?{?node?}?=?path;
        ????const?i18nKey?=?findI18nKey(node.value);
        ????if?(i18nKey)?{
        ??????node.value?=?`{${I18_FUNC}("${i18nKey}")}`;
        ????}
        ??},
        ??//?Literal找到的可能是函數(shù)中調(diào)用參數(shù)的文字?也可能是jsx屬性中的文字
        ??Literal(path)?{
        ????const?{?node?}?=?path;
        ????const?i18nKey?=?findI18nKey(node.value);
        ????if?(i18nKey)?{
        ??????if?(path.parent.type?===?'JSXAttribute')?{
        ????????path.replaceWith(
        ??????????t.jsxExpressionContainer(makeCallExpression(I18_FUNC,?i18nKey))
        ????????);
        ??????}?else?{
        ????????if?(t.isStringLiteral(node))?{
        ??????????path.replaceWith(makeCallExpression(I18_FUNC,?i18nKey));
        ????????}
        ??????}
        ????}
        ??},
        });

        當(dāng)然,實(shí)際項(xiàng)目中還需要考慮文案翻譯的部分,如何建立平臺(tái),如何和運(yùn)營或者翻譯專員協(xié)作。

        以及 AST 處理各種各樣的邊界情況,肯定要復(fù)雜的多,以上只是簡化版的思路。

        總結(jié)

        進(jìn)入大廠搬磚也有 3 個(gè)月了,對(duì)這里的感受就是人才的密度是真的很高,可以看到社區(qū)的很多大佬在內(nèi)部前端群里討論最前沿的問題,甚至如果你和他在一個(gè)樓層,你還可以現(xiàn)實(shí)里跑過去和他面基,請(qǐng)教問題,這種感覺真的很棒。有一次我遇到了一個(gè) TS 上的難題,就直接去對(duì)面找某個(gè)知乎上比較出名的大佬討論解決(厚臉皮)。

        在之后的工作中,對(duì)于學(xué)到的知識(shí)點(diǎn)我也會(huì)進(jìn)行進(jìn)一步的總結(jié),發(fā)一些有價(jià)值的文章,感興趣的話歡迎關(guān)注~


        掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。



        你點(diǎn)的每個(gè)贊,我都認(rèn)真當(dāng)成了喜歡
        瀏覽 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>
            91免费国产精品 | 国产卡一卡二在线观看 | 操中国美女全黄毛片 | aaa女郎大尺度写真视频54 | 女人被爽到呻吟娇喘的视频动态图 | 污污网站在线看 | 欧美精品人妻一区二区免费视频 | 欧美日韩一区二区三区不卡 | 射死你天天日 | 《中国裸体写真》30集在线观看 |