<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>

          讓你 React 組件水平暴增的 5 個(gè)技巧

          共 8208字,需瀏覽 17分鐘

           ·

          2023-07-19 17:33

          最近看了一些 Ant Design 的組件源碼,學(xué)到一些很實(shí)用的技巧,這篇文章來分享一下。

          首先,我們用 create-react-app 創(chuàng)建個(gè) React 項(xiàng)目(選擇 typescript 模版):

                
                npx?create-react-app?--template=typescript?component-test
          ae2bcb7e71f48c00a79d0e3a694fd0d0.webp

          進(jìn)入項(xiàng)目目錄,把開發(fā)服務(wù)跑起來:

                
                npm?run?start
          06a3c9a4b0c2faf14f42d08a8d09979e.webp

          然后引入 antd:

                
                npm?install?--save?antd

          在 App.tsx 里引入幾個(gè) antd 組件:

          d16f72d3b82c4c830d6886143a51086b.webp

          頁面上可以看到這倆組件都成功渲染了:

          3e697fae84c29cb3d73b99dc1bf6c3b3.webp

          然后我們來看一下 Ant Design 組件里的一些技巧:

          透?jìng)?className、style

          我們可以給組件設(shè)置 className 和 style:

                
                import?'./App.css';
          import?{?Button?}?from?'antd';

          function?App()?{
          ??return?(
          ????<div?className="App">
          ????????<Button?className="aaa?bbb"?style={{
          ??????????width:?'100px',
          ??????????height:?'50px'
          ????????}}?type="primary">測(cè)試</Button>
          ????</
          div>
          ??);
          }

          export?default?App;

          在頁面里打開 DevTools 可以看到 className 和 style 都被設(shè)置到了 button 上。

          c11197993ba065b49686f03c5dfb999b.webp

          這種功能的實(shí)現(xiàn)就是透?jìng)?className 和 style 的 props。

          基本 antd 所有的組件都會(huì)做這個(gè)。

          比如 VisualList 組件的源碼:

          3a9ba2441056a4023f3278647f6b7ead.webp

          它取了傳入的 className、style 的 props,還有剩余的所有 props。

          對(duì) className 做了一些處理,添加了兩個(gè) className:

          371d3073eff594054420f8cb194375d3.webp

          對(duì) style 也做了擴(kuò)展,添加了個(gè) position: relative 的樣式。

          7466ab0072743780cdebed582fe744db.webp

          然后把 style、className,額外的 props 都設(shè)置給最外層的 div。

          這樣,使用這個(gè)組件的時(shí)候,就可以自己定義一些樣式,設(shè)置一些 props。

          其中,classnames 是用來動(dòng)態(tài)產(chǎn)生 className 的一個(gè)包,用起來很簡(jiǎn)單。

          比如這樣調(diào)用:

                
                classNames('aaa',?{?bbb:?true,?ccc:?false?},?false,?{?eee:?true?});?

          那么最終的 className 就是 'aaa bbb eee'。

          這樣,組件用起來體驗(yàn)就和 html 標(biāo)簽差不多,可以自己控制一些樣式。

          這樣寫 props 的類型的時(shí)候,也是直接用了 html 標(biāo)簽的類型。

          比如這個(gè) List 的參數(shù)就繼承了 React.HTMLArrtibutes<any>,也就是任意 html 標(biāo)簽的屬性:

          aac7c25df389f0bf8e20d680e8eada5c.webp

          當(dāng)然,children 屬性是不可以設(shè)置的。因?yàn)?React 用 children 參數(shù)來傳遞子組件。

          比如 form 組件:

          它的參數(shù)是繼承了 React.FormHTMLAttributes<HTMLFormElement>:

          31b9797f926a8a73a3cffd4a3f056859.webp

          去掉了 children 和 onSubmit 這倆屬性,因?yàn)檫@倆是 From 組件的參數(shù)。

          也就是說:antd 的組件基本都支持傳入 className、style 或者任何 html 標(biāo)簽的 props,會(huì)透?jìng)?props 到組件內(nèi)的容器標(biāo)簽,所以用起來體驗(yàn)和原生標(biāo)簽很類似。但這也要求 props 實(shí)現(xiàn) React.FormHTMLAttributes 的 type。

          通過 forwardRef 暴露一些方法

          外界控制組件的方式就是通過傳 props,但有時(shí)候想調(diào)用組件的一些方法呢?

          這時(shí)候就需要 ref 了。

          我們先來試一下 ref:

          685b16ae38976b918013bdef822f5967.webp

          通過 useRef 創(chuàng)建個(gè) ref 對(duì)象,然后把 input 標(biāo)簽設(shè)置到 ref。

          在 useEffect 里就可以調(diào)用 input 的方法了:

          43615e6d87af9b8860bb5d4ac1b8937d.webp

          但這是原生標(biāo)簽,如果是組件呢?

          這時(shí)候就需要 forwardRef 了,也就是把組件內(nèi)的 ref 轉(zhuǎn)發(fā)一下。

          比如這樣:

                
                import?'./App.css';
          import?{?useRef?}?from?'react';
          import?{?useEffect?}?from?'react';
          import?React?from?'react';

          const?Guang:?React.ForwardRefRenderFunction<HTMLInputElement>?=?(props,?ref)?=>?{
          ??return?<div>
          ????<input?ref={ref}></input>
          ??</
          div>
          }

          const?WrapedGuang?=?React.forwardRef(Guang);

          function?App()?{
          ??const?ref?=?useRef<HTMLInputElement>(null);
          ?
          ??useEffect(()=>?{
          ????console.log('ref',?ref.current)
          ????ref.current?.focus()
          ??},?[]);

          ??return?(
          ????<div?className="App">
          ??????<WrapedGuang?ref={ref}/>
          ????</div>
          ??);
          }

          export?default?App;

          其實(shí) forwardRef 這個(gè) api 做的事情也很容易懂。

          就是把 ref 轉(zhuǎn)發(fā)到組件內(nèi)部來設(shè)置:

          2401621ba6355d37cbe8686d7763b9eb.webp

          這樣就把組件內(nèi)的 input 通過 ref 的方式傳遞到了組件外。

          效果和之前一樣:

          fdac22a55a0b563fa272cef78d69e860.webp

          不過被 forwardRef 包裹的組件的類型就要用 React.forwardRefRenderFunction 了:

          faffb53342992cb7059b9368a79e7eeb.webp

          第一個(gè)類型參數(shù)是 ref 的 content 的類型。

          但有的時(shí)候,我不是想把原生標(biāo)簽暴露出去,而是暴露一些自定義方法。

          這時(shí)候就需要 useImperativeHandle 的 hook 了。

          這樣寫:

                
                import?'./App.css';
          import?{?useRef?}?from?'react';
          import?{?useEffect?}?from?'react';
          import?React?from?'react';
          import?{?useImperativeHandle?}?from?'react';

          interface?RefProps?{
          ??aaa:?()?=>?void;
          }

          const?Guang:?React.ForwardRefRenderFunction<RefProps>?=?(props,?ref)?=>?{
          ??const?inputRef?=?useRef<HTMLInputElement>(null);

          ??useImperativeHandle(ref,?()?=>?{
          ????return?{
          ??????aaa()?{
          ????????inputRef.current?.focus();
          ??????}
          ????}
          ??});

          ??return?<div>
          ????<input?ref={inputRef}></input>
          ??</
          div>
          }

          const?WrapedGuang?=?React.forwardRef(Guang);

          function?App()?{
          ??const?ref?=?useRef<RefProps>(null);
          ?
          ??useEffect(()=>?{
          ????console.log('ref',?ref.current)
          ????ref.current?.aaa();
          ??},?[]);

          ??return?(
          ????<div?className="App">
          ??????<WrapedGuang?ref={ref}/>
          ????</div>
          ??);
          }

          export?default?App;

          也就是用 useImperativeHanlde 自定義了 ref 對(duì)象:

          1ea7e2e69fe57ebb9e4e6965ef47ccfe.webp

          小結(jié)一下:

          React 可以用 ref 保存原生標(biāo)簽,通過 ref.current 調(diào)用這個(gè)對(duì)象的屬性、方法。跨組件傳遞 ref 需要用 forwardRef 方法,如果你要進(jìn)一步自定義 ref,那就要用 useImperativeHandle 的 hook。

          然后看看 antd 組件是怎么用 ref 的。

          就如說 VisualList 組件:

          c3ef55b41adf07526289802fd97a679b.webp

          它也是包了一層 React.forwardRef,內(nèi)部用 useImperativeHandle 自定義了 ref:

          293bdb36ef307cda9ab25091b2e10d0b.webp

          這樣外部就可以調(diào)用這個(gè) ref 的方法了:

          a2f8e9e7002ce33942f23a17a4db3a03.webp

          再比如 Form 組件:

          它也是被 forwarRef 包裹的函數(shù)組件:

          c6a1ae605d8b12ecb27df45705f9e4cc.webp

          內(nèi)部用 useImperativeHandle 返回了自定義的對(duì)象:

          c4c2220ba8b8814b0d0af62baa24c6a4.webp

          所以你才可以這樣調(diào)用 form 組件的方法:

          f652da1ea24d112d3054cab7a6d4e0ad.webp

          這就是說:antd 的組件都會(huì)用 forwardRef 包裹一層,用來轉(zhuǎn)發(fā) ref,或者是轉(zhuǎn)發(fā)內(nèi)部的 html 標(biāo)簽的引用,或者是用 useImperativeHandle 自定義 ref 對(duì)象,來暴露一些方法。

          useCallback、useMemo

          useMemo 和 useCallback 是性能優(yōu)化相關(guān)的 hook。

          很多人不知道啥時(shí)候用,其實(shí)看下 antd 怎么用的就知道了:

          d7c7405f9591a200a76ae949ffc294ad.webp

          比如 VisualList 組件里計(jì)算 start、end、scrollHeight 這些值需要大量的計(jì)算。

          這些計(jì)算需要每次 render 都跑一遍么?

          不需要,只有在某些值變化的時(shí)候才需要重新計(jì)算。

          這時(shí)候用 React.useMemo 包裹就可以減少計(jì)算量,它只會(huì)在 deps 數(shù)組變化的時(shí)候執(zhí)行第一個(gè)參數(shù)的函數(shù)。

          useMemo 是 deps 變化之后重新執(zhí)行函數(shù)創(chuàng)建值,而 useCallback 并不會(huì)執(zhí)行函數(shù),它只是在 deps 變化的時(shí)候返回第一個(gè)參數(shù)的函數(shù):

          8819c24653d295b6e5093828c80b8f24.webp

          這樣有什么用呢?

          react 重新渲染的依據(jù)是 props 是否有變化,如果每次都創(chuàng)建新的函數(shù),那是不是每次都會(huì)重新渲染?

          所以用 useCallback 包裹的函數(shù)參數(shù),就可以在 deps 沒變的時(shí)候,始終返回同一個(gè)函數(shù),這樣避免了沒必要的渲染。

          當(dāng)然,useMemo 也有這個(gè)作用:

          比如說 Form 組件源碼里的這個(gè) useMemo:

          740f9f74794771a8a189c47e84064d08.webp

          你說它是為了減少計(jì)算量么?

          并不是,它沒有做任何計(jì)算,只是把參數(shù)原封不動(dòng)返回了。

          這也同樣是為了避免 props 變化。

          也就是說:antd 里很多地方都用了 useMemo 和 useCallback 來進(jìn)行渲染性能優(yōu)化。useMemo 只有在 deps 數(shù)組變化的時(shí)候才會(huì)執(zhí)行第一個(gè)函數(shù),返回新的值,可以用來減少不必要的計(jì)算,也可以保證 props 不變來避免不要的渲染。useCallback 是只有 deps 數(shù)組變化的時(shí)候才返回第一個(gè)函數(shù)的值,可以保證 props 不變來用來避免不必要的渲染

          用 Context 來跨組件傳遞值

          antd 里很多配置的傳遞都是通過 Context。

          比如 disabled 的設(shè)置:

          7186f6d0c08ceda863665ef538a33a45.webp

          通過 React.createContext 創(chuàng)建 context 對(duì)象,通過 Provider 修改 context 的值。

          在最外層包裹這個(gè) Provider 組件來修改 context 值:

          632aadc7da1d8e0857c780e90f4b39c1.webp

          然后你可以在任意的組件把 context 值取出來用:

          c6833f12af2120ba0275bf277135d297.webpfbb1c03c21e5380489a31e61efb82090.webp

          像什么主題、大小等配置,都是通過 Context 傳遞的。

          除了用來傳遞配置外,很多組件也依賴 Context 來傳遞一些值,比如 Form:

          84de40c9ecad05771f13a25fcfe7af73.webp

          在 Form 組件里設(shè)置 form 對(duì)象,然后 setFieldValue 設(shè)置字段值。

          為什么 Form.Item 里加個(gè) name 就可以取出來了呢?

          我并沒有傳遞 form 參數(shù)過去呀?

          很明顯,這里也是用 Context 來傳遞的:

          antd 會(huì)創(chuàng)建這樣一個(gè) context 對(duì)象:

          27aaec0f124dfdc157131d894aad5745.webp

          然后在外層用 Provider 設(shè)置 context 值:

          3066ac81c8af156f897969d4e123e8f7.webp

          也就是我們這里傳的 form:

          34d735458f2d719665652f1649293bb6.webp

          那 Form.Item 里自然可以拿到 context 的值,從而取到具體字段信息了:

          c3c372a5b742f429ec738b903d80cdd8.webp37d4d8d31b00adae8020acb85425612a.webp

          也就是說:antd 里大量用到了 Context,除了用來傳遞 config、theme、size 等全局配置信息外,還用來跨組件傳遞數(shù)據(jù),比如 Form、Form.Item 組件,就是通過 Provider、useContext 來存取值的。

          React.Children、React.cloneElement

          React 組件可以設(shè)置內(nèi)容,在組件內(nèi)通過 props.children 來取。

                
                import?React?from?'react';

          interface?GuangProps?{
          ??children:?React.ReactNode[];
          }

          const?Guang:?React.FunctionComponent<GuangProps>?=?(props)?=>?{
          ??console.log(props);
          ??return?<div?className="guang">
          ????{props.children}
          ??</div>
          }

          function?App()?{
          ??return?(
          ????<div?className="App">
          ??????<Guang>
          ????????<p>111</
          p>
          ????????<p>222</p>
          ??????</
          Guang>
          ????</div>
          ??);
          }

          export?default?App;

          比如我在組件里把 props.children 取出來,放到 className 為 guang 的 div 下:

          4db1a68f0f81a8c018a8bcbaef662573.webp43f9d88ea07c74393f0da63d117702fe.webp

          如果想對(duì)這些 children 做一些操作,就需要用 React.Children 的 api 了,比如 React.Children.toArray、React.Children.forEach、React.Children.map

          有同學(xué)說,props.children 本來就是數(shù)組啊,直接操作不就行了?

          不行的,直接操作有一些問題,比如我 sort 一下:

          c8fe33df3131a3cdef9900371addfdf2.webp

          會(huì)報(bào)錯(cuò):

          4bdee2b9bee4ffb4099ab3b91959f4ce.webp

          所以 props.children 不能直接當(dāng)做數(shù)組用,需要 toArray 一下:

          b298fc15cf31ffffc7382f370953d2fd.webp

          這樣就沒有報(bào)錯(cuò)了:

          f5e8f64b7059944d9478571e332eecfe.webp

          同理,React.Children 的 forEach 和 map 也很容易理解。

          而且還可以用 React.cloneElement 復(fù)制下傳入的 ReactElement。

          比如這樣:

          b3f952a06e473c8267441565ef325dea.webp

          用 React.Children.map 遍歷 children,對(duì)每個(gè) child 復(fù)制一份出來,修改下 props ,并且添加一個(gè) children。

          效果是這樣的:

          752ca1824a745f0f1c598bc983bceb4c.webp

          React.cloneElement 的第二個(gè)參數(shù)是修改的 props,后面的參數(shù)是 children:

          e25f03aac3f7dc4bcd206b6b44aaa2d1.webp

          結(jié)合 React.Children 的 api 和 React.cloneElement 的 api 就可以任意修改 children 渲染的結(jié)果。

          在 antd 里也有大量運(yùn)用:

          比如 button 組件里,通過 map + cloneElement 來處理中文字符的問題:

          5cf41aeae17f6f5e662d0e3dfd3cf2cd.webp

          或者用 map + cloneElement 給 child 的 children 外包一層組件:

          5cddc28377a6207f032c5ab157818ce2.webp

          更巧妙的是 VirtualList 里的應(yīng)用:

          e5f51521d5ad294f2f31220ac81610bd.webp

          你不需要給傳入的 children 設(shè)置 ref,antd 會(huì)通過 map + cloneElement 給你加上 ref 的 props,然后在回調(diào)函數(shù)里把這個(gè) ref 保存下來。

          這樣就拿到了你傳入的每一個(gè) children 的 ref。

          比如根據(jù) key 來保存每個(gè) Item 的 ref:

          28b0ec6ef0f7369e884165f0a325e5b1.webp

          也就是說:antd 組件里大量用到了 React.Children + React.cloneElement 的 api 對(duì) props.children 做一些修改,比如包一層組件、添加 ref 等參數(shù)、添加一些 children 等。

          總結(jié)

          這篇文章總結(jié)了 ant design 組件源碼里的 5 個(gè)技巧:

          • 透?jìng)?className、style,還有其他 html 標(biāo)簽的 props,讓你的組件用起來體驗(yàn)和原生 html 標(biāo)簽一樣
          • 通過 forwardRef + useImperativeHandle 暴露一些方法,每個(gè)組件都可以通過 ref 暴露一些 api 出來
          • useCallback、useMemo 緩存計(jì)算結(jié)果,通過讓 props 不變來減少?zèng)]必要的渲染
          • 用 Context 的 Provider + useContext 來跨組件傳遞值,可以用來傳遞全局配置,也可以用來做業(yè)務(wù)組件的跨層傳遞數(shù)據(jù)
          • 通過 React.Children + React.cloneElement 的 api 對(duì) props.children 做各種修改

          這些都是在 antd 里隨處可見的技巧,可以說任何一個(gè)組件里都有這些東西。

          這些寫 React 組件的技巧你都用過么?沒用過的話不妨從今天開始用起來吧。


          瀏覽 50
          點(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>
                  a视频在线看 | 日韩精品一级毛片 | 成人黄色性生活视频 | 国产激情婷婷 | 婷色五月天 |