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

          如何使用 useRef 優(yōu)化 React 性能問題

          共 3708字,需瀏覽 8分鐘

           ·

          2023-10-31 07:48

          • 原文:How to useRef to Fix React Performance Issues

          • 原文作者:Sidney Alcantara

          • 譯文出自:掘金翻譯計(jì)劃

          • 本文永久鏈接:https://github.com/xitu/gold-miner/blob/master/article/2020/how-to-useref-to-fix-react-performance-issues.md

          • 譯者:NieZhuZhu「彈鐵蛋同學(xué)」

          • 校對者:regon-cao、zenblo

          如何使用 useRef 修復(fù) React 性能問題

          Refs 是 React 中很少會使用到的特性。如果你已經(jīng)讀過了官方的 React Ref Guide,你會從中了解到 Refs 被描述為重要的 React 數(shù)據(jù)流的 “逃生艙門”,需謹(jǐn)慎使用。Refs 被視為訪問組件的基礎(chǔ) DOM 元素的正確方法。

          伴隨著 React Hooks 的到來,React 團(tuán)隊(duì)引入了 [useRef](https://reactjs.org/docs/hooks-reference.html#useref) Hook,它擴(kuò)展了這個功能:

          useRef() 比 ref 屬性更有用。它通過類似在 class 中使用實(shí)例字段的方式,非常方便地 保存任何可變值。” —— React 文檔

          新的 React Hooks API 發(fā)布的時候,我的確忽略了這一點(diǎn),事實(shí)證明 useRef 真的非常有用。

          面臨的問題

          我是一名 Firetable 的軟件開發(fā)工程師。Firetable 是一個開源的 React 電子表格應(yīng)用,結(jié)合了 Firestore 和 Firebase 的主要功能。其中有一個主要功能是側(cè)面抽屜,它是一種類似于窗體的 UI,用于編輯在主表上滑動的那一行。

          當(dāng)用戶單擊選中表格中的某一個單元格時,可以通過打開側(cè)抽屜的方式編輯該單元格所對應(yīng)的行數(shù)據(jù)。換句話說,我們在側(cè)邊抽屜中渲染的內(nèi)容取決于當(dāng)前選擇的行 —— 我們需要將這行的數(shù)據(jù)狀態(tài)記錄下來。

          將這行數(shù)據(jù)的狀態(tài)的放在側(cè)抽屜組件內(nèi)部是最符合邏輯的,因?yàn)楫?dāng)用戶選擇其他單元格時,它應(yīng)該影響側(cè)邊的抽屜組件。然而:

          • 我們需要在表格組件里設(shè)置這個數(shù)據(jù)狀態(tài)。我們用的是 [react-data-grid](https://github.com/adazzle/react-data-grid) 渲染表格,并且它接收一個當(dāng)用戶點(diǎn)擊一個單元格時會觸發(fā)的回調(diào)。就目前來看,這是我們能從表格中獲取選中行數(shù)據(jù)的唯一途徑。

          • 但是側(cè)邊抽屜組件和表格組件是同級(兄弟)組件,所以不能直接訪問彼此的數(shù)據(jù)狀態(tài)。

          • React 的推薦做法是 提升狀態(tài) 到倆組件最近的父級節(jié)點(diǎn) (以這個為例,父級節(jié)點(diǎn)為 TablePage)。但是我們決定不將狀態(tài)遷移到這個組件,理由是:

          1. TablePage 不保存狀態(tài),主要是放置 table 和 side drawer 組件的容器, 兩者都不接收任何的 props。我們傾向于保持這種做法。

          2. 我們已經(jīng)在組件樹的頂層使用 React Context 來共享了許多的全局?jǐn)?shù)據(jù),并且我們覺得應(yīng)該將這個狀態(tài)上升到全局 store。

          注意:即使我們將數(shù)據(jù)狀態(tài)放在了 TablePage,無論如何我們都將面臨下面這個相同的問題。

          問題就是每當(dāng)用戶選擇一個單元格或打開側(cè)面抽屜時,全局 context 的更新會使得整個應(yīng)用發(fā)生重新渲染。table 組件可以一次顯示數(shù)十個單元格,并且每個單元格都有自己的編輯器組件。這會導(dǎo)致大約 650ms 的渲染時間,這個時間太長以至于在打開側(cè)邊抽屜的時候會感受到明顯的延遲。

          罪魁禍?zhǔn)资?context —— 這就是為什么要在 React 中使用而不是在全局 JavaScript 對象中使用:

          ”只要提供給 Provider 的值發(fā)生變化,所有消費(fèi)到了 Provider 的后代組件都會發(fā)生重渲染。“ — React Context

          到目前為止,雖然我們已經(jīng)足夠了解 React 的狀態(tài)和生命周期,但現(xiàn)在看來我們依舊陷入了困境。

          頓悟時刻

          在決定使用 useRef 之前,我們嘗試了幾種不同的解決方案。(Dan Abramov 的文章) :

          1. 拆分 context (也就是創(chuàng)建新的 SideDrawerContext) —— table 組件仍然會消費(fèi)到新的 context,在打開側(cè)邊抽屜的時候依舊會 導(dǎo)致 table 組件的不必要的重新渲染。

          2. 將 table 組件放在 React.memo 或 useMemo 中 —— table 組件依舊是需要通過 useContext 拿到側(cè)邊抽屜組件的狀態(tài),兩種 API 均無法阻止其重新渲染。

          3. 將用于渲染表格的 react-data-grid 組件進(jìn)行 memo —— 這將使我們的代碼更加的冗長。我們還發(fā)現(xiàn)它阻止了 “必要” 的重新渲染,要求我們花費(fèi)更多的時間完全修復(fù)或者重構(gòu)我們的代碼來實(shí)現(xiàn)側(cè)邊抽屜。

          當(dāng)再次閱讀 Hook APIs 和 useMemo 文檔的時候,我終于遇到了 useRef 相關(guān)內(nèi)容。

          useRef() 比 ref 屬性更有用。它通過像在 class 中使用實(shí)例字段的方式,非常方便地 保存任何可變值。” —— React 文檔

          更重要的是:

          “當(dāng) ref 對象內(nèi)容發(fā)生變化時,useRef 并不會通知變更。變更 .current 屬性不會引發(fā)組件重新渲染。” —— React 文檔

          此時:我們不需要存儲側(cè)抽屜的狀態(tài)。我們只需要引用設(shè)置該狀態(tài)的函數(shù)即可。

          解決方案

          1. 將打開狀態(tài)和單元狀態(tài)保存在側(cè)面抽屜組件中。

          2. 創(chuàng)建這些狀態(tài)的 ref,并將其存儲在 context 中。

          3. 當(dāng)用戶單擊單元格時,使用之前說的表中的回調(diào)去調(diào)用 ref 設(shè)置數(shù)據(jù)狀態(tài)的函數(shù)(在側(cè)抽屜內(nèi))。

          以下代碼是在 Firetable 使用的代碼縮寫版,其中包括了 ref 和 TypeScript 的類型:

          import { SideDrawerRef } from 'SideDrawer'
          export function FiretableContextProvider({ children }) { const sideDrawerRef = useRef<SideDrawerRef>();
          return ( <FiretableContext.Provider value={{ sideDrawerRef }}> {children} </FiretableContext.Provider> )}

          注意:由于函數(shù)組件在重新渲染時會運(yùn)行整個函數(shù)體,所以每當(dāng) “單元” 或 “打開” 狀態(tài)更新(并導(dǎo)致重新渲染)時,“sideDrawerRef” 總是能在 “.current” 中獲取到最新值。

          事實(shí)證明,此解決方案是最佳的:

          1. 當(dāng)前的單元格和打開的狀態(tài)存儲在側(cè)面抽屜組件中 —— 這是放置它的最合邏輯的地方。

          2. 當(dāng)需要時,表格組件也可以訪問其兄弟組件的狀態(tài)。

          3. 當(dāng)前單元格或打開狀態(tài)更新時,它只會觸發(fā)側(cè)抽屜組件的重新渲染,而不觸發(fā)整個應(yīng)用程序中的其他組件重新渲染。

          你可以在 Firetable GitHub 源碼中看它是如何被使用的。

          什么時候使用 useRef

          不過,這并不意味著您可以在應(yīng)用中隨意使用。當(dāng)您需要在特定時間訪問或更新另一個組件的狀態(tài),但是您的其他組件不依賴于該狀態(tài)或基于該狀態(tài)進(jìn)行呈現(xiàn)時,這是最好的辦法。React 的提升狀態(tài)和單向數(shù)據(jù)流的核心概念足以覆蓋大多數(shù)應(yīng)用程序架構(gòu)。

          ?? 今天的文章分享就到這里啦,如果喜歡這篇文章的話請點(diǎn)贊、在看、轉(zhuǎn)發(fā)關(guān)注我吧 ??

          瀏覽 722
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  人人操,人人 | 操操操操操操操操操逼 | 亚洲av大全 | 西西www444大胆无码视频 | 亚洲热在线视频 |