改進和優化 React 應用程序性能的方法

已發表: 2022-03-10
快速總結 ↬自 React 推出以來,它改變了前端開發人員構建 Web 應用程序的方式,其虛擬 DOM 以有效渲染組件而聞名。 在本教程中,我們將討論在 React 應用程序中優化性能的各種方法,以及我們可以用來提高性能的 React 特性。

React 使 Web 應用程序能夠快速更新其用戶界面 (UI),但這並不意味著您的中型或大型 React 應用程序將有效地執行。 它的性能將取決於您在構建它時如何使用 React,以及您對 React 的運行方式以及組件在其生命週期的各個階段所經歷的過程的理解。 React 為 Web 應用程序提供了許多性能改進,您可以通過各種技術、功能和工具來實現這些改進。

在本教程中,我們將討論在 React 應用程序中優化性能的各種方法,以及我們可以用來提高性能的 React 特性。

在 React 應用程序中從哪裡開始優化性能?

如果不確切知道優化的時間和地點,我們就無法開始優化應用程序。 你可能會問,“我們從哪裡開始?”

在初始渲染過程中,React 構建組件的 DOM 樹。 因此,當 DOM 樹中的數據發生更改時,我們希望 React 僅重新渲染受更改影響的那些組件,跳過樹中未受影響的其他組件。

然而,React 最終可能會重新渲染 DOM 樹中的所有組件,即使並非所有組件都受到影響。 這會導致加載時間變長,浪費時間,甚至浪費CPU資源。 我們需要防止這種情況發生。 因此,這是我們將重點關注優化工作的地方。

在這種情況下,我們可以將每個組件配置為僅在必要時渲染或區分,以避免浪費資源和時間。

跳躍後更多! 繼續往下看↓

衡量績效

永遠不要根據你的感受開始你的 React 應用程序的優化過程。 相反,使用可用的測量工具來分析您的 React 應用程序的性能,並獲得可能會減慢它的詳細報告。

使用 Chrome 的性能選項卡分析 React 組件

根據 React 的文檔,當您仍處於開發模式時,您可以使用 Chrome 瀏覽器中的“性能”選項卡來可視化 React 組件如何安裝、更新和卸載。 例如,下圖顯示了 Chrome 的“性能”選項卡在開發模式下分析和分析我的博客。

性能分析器摘要
性能分析器摘要(大預覽)

為此,請按照下列步驟操作:

  1. 暫時禁用所有擴展,尤其是 React 開發者工具,因為它們會干擾分析結果。 您可以通過在隱身模式下運行瀏覽器來輕鬆禁用擴展程序。
  2. 確保應用程序在開發模式下運行。 也就是說,應用程序應該在您的本地主機上運行。
  3. 打開 Chrome 的開發者工具,點擊“性能”標籤,然後點擊“記錄”按鈕。
  4. 執行您要分析的操作。 錄製時間不要超過 20 秒,否則 Chrome 可能會掛起。
  5. 停止錄音。
  6. React 事件將分組在“用戶計時”標籤下。

分析器中的數字是相對的。 大多數時間和組件將在生產中更快地呈現。 儘管如此,這應該可以幫助您確定何時錯誤地更新了 UI,以及 UI 更新發生的深度和頻率。

React 開發者工具分析器

根據 React 的文檔,在react-dom 16.5+ 和react-native 0.57+ 中,使用 React Developer Tools Profiler 在開發人員模式下可以使用增強的分析功能。 Profiler 使用 React 的實驗性 Profiler API 來整理每個渲染組件的時間信息,以便識別 React 應用程序中的性能瓶頸。

只需為您的瀏覽器下載 React Developer Tools,然後您就可以使用它附帶的分析器工具。 分析器只能在開發模式或 React v16.5+ 的生產分析構建中使用。 下圖是我的博客在開發模式下使用 React Developer Tools Profiler 的分析器摘要:

React Developer Tools Profiler 火焰圖
React Developer Tools Profiler 火焰圖(大預覽)

為此,請按照下列步驟操作:

  1. 下載 React 開發者工具。
  2. 確保您的 React 應用程序處於開發模式或 React v16.5+ 的生產分析版本中。
  3. 打開 Chrome 的“開發者工具”選項卡。 React Developer Tools 提供了一個名為“Profiler”的新選項卡。
  4. 單擊“記錄”按鈕,然後執行您要配置的操作。 理想情況下,在您執行了要分析的操作後停止錄製。
  5. 一個圖表(稱為火焰圖)將與您的 React 應用程序的所有事件處理程序和組件一起出現。

注意有關詳細信息,請參閱文檔。

使用React.memo()進行記憶

React v16 發布了一個額外的 API,一個名為React.memo()的高階組件。 根據文檔,這僅作為性能優化存在。

它的名字“ memo ”來自 memoization,它基本上是一種優化形式,主要用於通過存儲昂貴函數調用的結果並在再次調用相同的昂貴函數時返回存儲的結果來加速代碼。

記憶是一種執行一次函數的技術,通常是一個純函數,然後將結果保存在內存中。 如果我們嘗試使用與之前相同的參數再次執行該函數,它將僅返回第一個函數執行的先前保存的結果,而無需再次執行該函數。

將上面的描述映射到 React 生態系統,提到的函數是 React 組件,參數是 props。

使用React.memo()聲明的組件的默認行為是僅在組件中的 props 發生更改時才呈現。 它對道具進行了淺層比較以檢查這一點,但可以使用一個選項來覆蓋它。

React.memo()通過避免重新渲染 props 未更改或不需要重新渲染的組件來提高 React 應用程序的性能。

下面的代碼是React.memo()的基本語法:

 const MemoizedComponent = React.memo((props) => { // Component code goes in here })

何時使用React.memo()

  • 純功能組件
    你可以使用React.memo()如果你的組件是功能性的,被賦予相同的道具,並且總是呈現相同的輸出。 您還可以在帶有 React 鉤子的非純功能組件上使用React.memo()
  • 組件經常渲染
    你可以使用React.memo()來包裝一個經常渲染的組件。
  • 組件使用相同的道具重新渲染
    使用React.memo()包裝在重新渲染期間通常提供相同道具的組件。
  • 中高元素
    將它用於包含中到大量 UI 元素的組件,以檢查 props 是否相等。

注意在記憶使用 props 作為回調的組件時要小心。 確保在渲染之間使用相同的回調函數實例。 這是因為父組件可以在每次渲染時提供不同的回調函數實例,這將導致記憶過程中斷。 要解決此問題,請確保 memoized 組件始終接收相同的回調實例。

讓我們看看我們如何在現實世界中使用記憶。 下面的功能組件,稱為“照片”,使用React.memo()來防止重新渲染。

 export function Photo({ title, views }) { return ( <div> <div>Photo title: {title}</div> <div>Location: {location}</div> </div> ); } // memoize the component export const MemoizedPhoto = React.memo(Photo);

上面的代碼由一個功能組件組成,該組件顯示一個包含照片標題和照片中主題位置的 div。 我們還通過創建一個新函數並將其命名為MemoizedPhoto來記憶組件。 只要在後續渲染中 props、 titlelocation相同,記憶 photo 組件將防止該組件重新渲染。

 // On first render, React calls MemoizedPhoto function. <MemoizedPhoto title="Effiel Tower" location="Paris" /> // On next render, React does not call MemoizedPhoto function, // preventing rendering <MemoizedPhoto title="Effiel Tower" location="Paris" />

在這裡,React 只調用了 memoized 函數一次。 只要道具保持不變,它就不會在下一次調用中渲染組件。

捆綁和縮小

在 React 單頁應用程序中,我們可以將所有 JavaScript 代碼打包並壓縮到一個文件中。 這個沒問題,只要我們的應用比較小。

隨著我們的 React 應用程序的增長,將我們所有的 JavaScript 代碼捆綁和壓縮到一個文件中變得有問題、難以理解和乏味。 它還會影響我們的 React 應用程序的性能和加載時間,因為我們正在向瀏覽器發送一個大的 JavaScript 文件。 因此,我們需要一些過程來幫助我們將代碼庫拆分為各種文件,並根據需要按時間間隔將它們交付給瀏覽器。

在這種情況下,我們可以使用某種形式的資產捆綁器,例如 Webpack,然後利用其代碼拆分功能將我們的應用程序拆分為多個文件。

Webpack 的文檔中建議將代碼拆分作為改善應用程序加載時間的一種方法。 React 的文檔中也建議延遲加載(只提供用戶當前需要的東西),這可以顯著提高性能。

Webpack 提出了三種通用的代碼拆分方法:

  • 入口點
    使用條目配置手動拆分代碼。
  • 重複預防
    使用SplitChunksPlugin去重複和拆分塊。
  • 動態導入
    通過模塊內的內聯函數調用拆分代碼。

代碼拆分的好處

  • 拆分代碼有助於瀏覽器的緩存資源和不經常更改的代碼。
  • 它還有助於瀏覽器並行下載資源,從而減少應用程序的整體加載時間。
  • 它使我們能夠將代碼拆分成塊,這些塊將根據需要或應用程序的需要加載。
  • 它使首次渲染時資源的初始下載相對較小,從而減少了應用程序的加載時間。
捆綁和縮小過程
捆綁和縮小過程(大預覽)

不可變數據結構

React 的文檔談到了不改變數據的力量。 任何無法更改的數據都是不可變的。 不變性是 React 程序員應該理解的概念。

不可變的值或對像不能更改。 因此,當有更新時,會在內存中創建一個新值,而舊值保持不變。

我們可以使用不可變數據結構和React.PureComponent來自動檢查復雜的狀態變化。 例如,如果您的應用程序中的狀態是不可變的,您實際上可以使用 Redux 之類的狀態管理庫將所有狀態對象保存在單個存儲中,從而使您能夠輕鬆實現撤消和重做功能。

不要忘記,一旦創建不可變數據,我們就無法更改它。

不可變數據結構的好處

  • 它們沒有副作用。
  • 不可變數據對象易於創建、測試和使用。
  • 它們幫助我們編寫可用於快速檢查狀態更新的邏輯,而無需一遍又一遍地檢查數據。
  • 它們有助於防止時間耦合(一種代碼依賴於執行順序的耦合)。

以下庫有助於提供一組不可變的數據結構:

  • 不變性助手
    在不更改源的情況下更改數據副本。
  • 不可變的.js
    JavaScript 的不可變持久數據集合提高了效率和簡單性。
  • 無縫不可變
    JavaScript 的不可變數據結構與普通的 JavaScript 數組和對象向後兼容。
  • 反應複製寫入
    這提供了具有可變 API 的不可變狀態。

其他提高性能的方法

在部署之前使用生產構建

React 的文檔建議在部署應用程序時使用縮小的生產構建。

React 開發者工具的“生產構建”警告
React 開發者工具的“生產構建”警告(大預覽)

避免匿名函數

因為匿名函數沒有被分配一個標識符(通過const/let/var ),所以當一個組件不可避免地再次被渲染時,它們就不是持久的。 這會導致 JavaScript 在每次重新渲染此組件時分配新內存,而不是像使用命名函數時那樣只分配一次內存。

 import React from 'react'; // Don't do this. class Dont extends Component { render() { return ( <button onClick={() => console.log('Do not do this')}> Don't </button> ); } } // The better way class Do extends Component { handleClick = () => { console.log('This is OK'); } render() { return ( <button onClick={this.handleClick}> Do </button> ); } }

上面的代碼顯示了兩種不同的方法來使按鈕在單擊時執行操作。 第一個代碼塊在onClick()屬性中使用了一個匿名函數,這會影響性能。 第二個代碼塊在onClick()函數中使用了一個命名函數,這在這個場景中是正確的方式。

安裝和拆卸組件通常很昂貴

不建議使用條件語句或tenaries 使組件消失(即卸載它),因為使組件消失會導致瀏覽器重繪和重排。 這是一個代價高昂的過程,因為必須重新計算文檔中 HTML 元素的位置和幾何形狀。 相反,我們可以使用 CSS 的opacityvisibility屬性來隱藏組件。 這樣,組件仍然在 DOM 中,但不可見,沒有任何性能成本。

虛擬化長列表

文檔建議,如果您要渲染包含大量數據的列表,則應在可見視口內一次渲染列表中的一小部分數據。 然後,您可以在列表滾動時呈現更多數據; 因此,數據僅在視口中時才會顯示。 這個過程稱為“窗口化”。 在窗口化中,在任何給定時間都會呈現一小部分行。 有一些流行的庫可以做到這一點,其中兩個由 Brian Vaughn 維護:

  • 反應窗口
  • 反應虛擬化

結論

還有其他幾種方法可以提高 React 應用程序的性能。 本文討論了性能優化的最重要和最有效的方法。

我希望你喜歡閱讀本教程。 您可以通過下面列出的資源了解更多信息。 如果您有任何問題,請將其留在下面的評論部分。 我很樂意回答每一個問題。

參考資料和相關資源

  • “優化性能”,React 文檔
  • “明智地使用 React.memo”,Dmitri Pavlutin
  • “React 中的性能優化技術”,Niteesh Yadav
  • “React 中的不變性:變異對像沒有錯”,Esteban Herrera
  • “優化 React 應用程序性能的 10 種方法”,Chidume Nnamdi
  • “提高 React 應用程序性能的 5 個技巧”,William Le