中文

探索高級 React memoization 技術,優化全球化應用的性能。學習何時及如何使用 React.memo、useCallback、useMemo 等來建構高效的用戶界面。

React Memo:深入探究面向全球化應用的優化技術

React 是一個用於建構使用者介面的強大 JavaScript 函式庫,但隨著應用程式變得越來越複雜,效能優化也變得至關重要。在 React 的優化工具箱中,一個不可或缺的工具就是 React.memo。本篇部落格文章將提供一份全面的指南,幫助您理解並有效利用 React.memo 及相關技術,為全球使用者打造高效能的 React 應用程式。

什麼是 React.memo?

React.memo 是一個高階組件 (Higher-Order Component, HOC),它可以對一個函數式組件進行記憶化 (memoize)。簡單來說,如果一個組件的 props 沒有改變,它就能防止該組件重新渲染。預設情況下,它會對 props 進行淺層比較 (shallow comparison)。這可以顯著提升效能,特別是對於那些渲染成本高昂或即使 props 保持不變也頻繁重新渲染的組件。

想像一個顯示使用者個人資料的組件。如果使用者的資訊(例如:姓名、頭像)沒有改變,就沒有必要重新渲染該組件。React.memo 讓您可以跳過這次不必要的重新渲染,從而節省寶貴的處理時間。

為什麼要使用 React.memo?

以下是使用 React.memo 的主要好處:

React.memo 的基本用法

使用 React.memo 非常簡單。只需用它來包裹您的函數式組件:

import React from 'react';

const MyComponent = (props) => {
 console.log('MyComponent rendered');
 return (
 
{props.data}
); }; export default React.memo(MyComponent);

在這個範例中,只有當 data prop 發生變化時,MyComponent 才會重新渲染。console.log 陳述式可以幫助您驗證組件實際重新渲染的時機。

理解淺層比較

預設情況下,React.memo 會對 props 進行淺層比較。這意味著它只檢查 props 的參考 (references) 是否改變,而不是檢查值本身。在處理物件和陣列時,理解這一點非常重要。

請看以下範例:

import React, { useState } from 'react';

const MyComponent = (props) => {
 console.log('MyComponent rendered');
 return (
 
{props.data.name}
); }; const MemoizedComponent = React.memo(MyComponent); const App = () => { const [user, setUser] = useState({ name: 'John', age: 30 }); const handleClick = () => { setUser({ ...user }); // Creating a new object with the same values }; return (
); }; export default App;

在這種情況下,儘管 user 物件的值(nameage)保持不變,但每次呼叫 handleClick 函數時都會建立一個新的物件參考。因此,React.memo 會認為 data prop 已經改變(因為物件參考不同),並將重新渲染 MyComponent

自訂比較函數

為了處理物件和陣列的淺層比較問題,React.memo 允許您提供一個自訂比較函數作為其第二個參數。這個函數接收兩個參數:prevPropsnextProps。如果組件 *不* 應該重新渲染(即 props 實際上是相同的),它應該返回 true;如果應該重新渲染,則返回 false

以下是如何在前述範例中使用自訂比較函數:

import React, { useState, memo } from 'react';

const MyComponent = (props) => {
 console.log('MyComponent rendered');
 return (
 
{props.data.name}
); }; const areEqual = (prevProps, nextProps) => { return prevProps.data.name === nextProps.data.name && prevProps.data.age === nextProps.data.age; }; const MemoizedComponent = memo(MyComponent, areEqual); const App = () => { const [user, setUser] = useState({ name: 'John', age: 30 }); const handleClick = () => { setUser({ ...user }); }; return (
); }; export default App;

在這個更新後的範例中,areEqual 函數會比較 user 物件的 nameage 屬性。現在,MemoizedComponent 只有在 nameage 改變時才會重新渲染。

何時使用 React.memo

React.memo 在以下情境中最為有效:

然而,必須注意的是,React.memo 並非萬靈丹。不加選擇地使用它實際上可能會損害效能,因為淺層比較本身也有成本。因此,對您的應用程式進行效能分析,找出最能從記憶化中受益的組件,是至關重要的。

React.memo 的替代方案

雖然 React.memo 是一個強大的工具,但它並不是優化 React 組件效能的唯一選擇。以下是一些替代方案和輔助技術:

1. PureComponent

對於類別組件 (class components),PureComponent 提供了與 React.memo 類似的功能。它會對 props 和 state 進行淺層比較,只有在發生變化時才會重新渲染。

import React from 'react';

class MyComponent extends React.PureComponent {
 render() {
 console.log('MyComponent rendered');
 return (
 
{this.props.data}
); } } export default MyComponent;

PureComponent 是手動實現 shouldComponentUpdate 的一個方便替代方案,後者是傳統上在類別組件中防止不必要重新渲染的方法。

2. shouldComponentUpdate

shouldComponentUpdate 是類別組件中的一個生命週期方法,它允許您定義自訂邏輯來決定組件是否應該重新渲染。它提供了最大的靈活性,但也需要更多的人工操作。

import React from 'react';

class MyComponent extends React.Component {
 shouldComponentUpdate(nextProps, nextState) {
 return nextProps.data !== this.props.data;
 }

 render() {
 console.log('MyComponent rendered');
 return (
 
{this.props.data}
); } } export default MyComponent;

雖然 shouldComponentUpdate 仍然可用,但由於其簡單性和易用性,PureComponentReact.memo 通常是更受歡迎的選擇。

3. useCallback

useCallback 是一個 React Hook,用於記憶化一個函數。它會返回該函數的記憶化版本,該版本只有在其依賴項之一發生變化時才會改變。這在將回呼函數 (callbacks) 作為 props 傳遞給被記憶化的組件時特別有用。

請看以下範例:

import React, { useState, useCallback, memo } from 'react';

const MyComponent = (props) => {
 console.log('MyComponent rendered');
 return (
 
 );
};

const MemoizedComponent = memo(MyComponent);

const App = () => {
 const [count, setCount] = useState(0);

 const handleClick = useCallback(() => {
 setCount(count + 1);
 }, [count]);

 return (
 

Count: {count}

); }; export default App;

在這個範例中,useCallback 確保了 handleClick 函數只有在 count 狀態改變時才會改變。如果不使用 useCallback,每次 App 渲染時都會建立一個新的函數,導致 MemoizedComponent 不必要地重新渲染。

4. useMemo

useMemo 是一個 React Hook,用於記憶化一個值。它會返回一個記憶化的值,該值只有在其依賴項之一發生變化時才會改變。這對於避免在每次渲染時都重新執行昂貴的計算非常有用。

import React, { useState, useMemo } from 'react';

const App = () => {
 const [input, setInput] = useState('');

 const expensiveCalculation = (str) => {
 console.log('Calculating...');
 let result = 0;
 for (let i = 0; i < str.length * 1000000; i++) {
 result++;
 }
 return result;
 };

 const memoizedResult = useMemo(() => expensiveCalculation(input), [input]);

 return (
 
setInput(e.target.value)} />

Result: {memoizedResult}

); }; export default App;

在這個範例中,useMemo 確保了 expensiveCalculation 函數只有在 input 狀態改變時才會被呼叫。這可以防止在每次渲染時都重新運行計算,從而顯著提升效能。

針對全球化應用的實用範例

讓我們看一些在全球化應用中如何應用 React.memo 及相關技術的實用範例:

1. 語言選擇器

語言選擇器組件通常會渲染一個可用語言的列表。這個列表可能相對靜態,意味著它不常變動。使用 React.memo 可以防止在應用程式的其他部分更新時,語言選擇器發生不必要的重新渲染。

import React, { memo } from 'react';

const LanguageItem = ({ language, onSelect }) => {
 console.log(`LanguageItem ${language} rendered`);
 return (
 
  • onSelect(language)}>{language}
  • ); }; const MemoizedLanguageItem = memo(LanguageItem); const LanguageSelector = ({ languages, onSelect }) => { return (
      {languages.map((language) => ( ))}
    ); }; export default LanguageSelector;

    在這個範例中,MemoizedLanguageItem 只有在 languageonSelect prop 改變時才會重新渲染。如果語言列表很長,或者 onSelect 處理程序很複雜,這將特別有益。

    2. 貨幣轉換器

    貨幣轉換器組件可能會顯示一個貨幣列表及其匯率。匯率可能會定期更新,但貨幣列表可能保持相對穩定。使用 React.memo 可以防止在匯率更新時,貨幣列表發生不必要的重新渲染。

    import React, { memo } from 'react';
    
    const CurrencyItem = ({ currency, rate, onSelect }) => {
     console.log(`CurrencyItem ${currency} rendered`);
     return (
     
  • onSelect(currency)}>{currency} - {rate}
  • ); }; const MemoizedCurrencyItem = memo(CurrencyItem); const CurrencyConverter = ({ currencies, onSelect }) => { return (
      {Object.entries(currencies).map(([currency, rate]) => ( ))}
    ); }; export default CurrencyConverter;

    在這個範例中,MemoizedCurrencyItem 只有在 currencyrateonSelect prop 改變時才會重新渲染。如果貨幣列表很長,或者匯率更新頻繁,這可以提升效能。

    3. 使用者個人資料顯示

    顯示使用者個人資料涉及展示靜態資訊,如姓名、個人頭像和可能的個人簡介。使用 `React.memo` 可確保該組件僅在使用者資料實際變更時才重新渲染,而不是在每次父組件更新時都渲染。

    import React, { memo } from 'react';
    
    const UserProfile = ({ user }) => {
     console.log('UserProfile rendered');
     return (
     

    {user.name}

    Profile

    {user.bio}

    ); }; export default memo(UserProfile);

    如果 `UserProfile` 是一個大型且頻繁更新的儀表板或應用程式的一部分,而使用者資料本身不常變更,這將特別有幫助。

    常見陷阱及如何避免

    雖然 React.memo 是一個有價值的優化工具,但了解並避免常見陷阱非常重要:

    對您的應用程式進行效能分析

    判斷 React.memo 是否確實提升了效能的最佳方法是,對您的應用程式進行效能分析。React 提供了多種分析工具,包括 React 開發者工具中的 Profiler 和 React.Profiler API。

    React 開發者工具的 Profiler 允許您記錄應用程式的效能追蹤,並識別出頻繁重新渲染的組件。React.Profiler API 則允許您以程式化方式測量特定組件的渲染時間。

    透過對您的應用程式進行效能分析,您可以找出最能從記憶化中受益的組件,並確保 React.memo 確實提升了效能。

    結論

    React.memo 是優化 React 組件效能的強大工具。透過防止不必要的重新渲染,它可以提升您應用程式的速度和反應能力,從而帶來更佳的使用者體驗。然而,明智地使用 React.memo 並對您的應用程式進行效能分析以確保它確實提升了效能,這一點非常重要。

    透過理解本篇部落格文章中討論的概念和技術,您可以有效地使用 React.memo 及相關技術,為全球使用者打造高效能的 React 應用程式,確保您的應用程式對世界各地的使用者都快速且反應靈敏。

    在優化您的 React 應用程式時,請記得考慮網路延遲和裝置能力等全球性因素。透過專注於效能和可及性,您可以創造出能為所有使用者(無論其地點或裝置)提供絕佳體驗的應用程式。

    進階閱讀與資源