技術 5 分鐘閱讀

React 效能優化實戰:memo、useMemo、useCallback 完整解析

React 效能優化實戰:memo、useMemo、useCallback 完整解析

前言

根據我的經驗,React 專案到了一定規模後,效能問題就會慢慢浮現。最近幫一個客戶優化後台系統,把列表頁的渲染時間從 800ms 降到 200ms。這篇整理一下用到的技巧。

先理解 React 的重新渲染機制

React 元件會在以下情況重新渲染:

  1. state 改變
  2. props 改變
  3. 父元件重新渲染

第三點最容易被忽略,也是效能問題的主要來源。

React.memo:避免不必要的重新渲染

interface ProductCardProps {
  name: string;
  price: number;
}

const ProductCard = React.memo(function ProductCard({ name, price }: ProductCardProps) {
  console.log('ProductCard rendered');
  return (
    <div className="rounded-lg border p-4">
      <h3>{name}</h3>
      <p>NT$ {price}</p>
    </div>
  );
});

React.memo 包裝後,只有當 props 真正改變時才會重新渲染。

何時使用?

  • 列表中的子元件(如商品卡片、表格行)
  • 渲染成本高的元件(含複雜計算或大量 DOM)
  • 父元件經常更新,但子元件的 props 不常變化

useMemo:快取計算結果

function OrderSummary({ items }: { items: OrderItem[] }) {
  const totalPrice = useMemo(() => {
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }, [items]);

  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => b.price - a.price);
  }, [items]);

  return (
    <div>
      <p>總金額:NT$ {totalPrice}</p>
      {sortedItems.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

何時使用?

  • 計算量大的運算(排序、篩選大量資料)
  • 需要保持物件參考穩定(傳給 memo 元件的 props)

useCallback:快取函式參考

function ProductList() {
  const [products, setProducts] = useState<Product[]>([]);
  const [query, setQuery] = useState('');

  const handleDelete = useCallback((id: number) => {
    setProducts(prev => prev.filter(p => p.id !== id));
  }, []);

  const handleSearch = useCallback((searchQuery: string) => {
    setQuery(searchQuery);
  }, []);

  return (
    <div>
      <SearchBar onSearch={handleSearch} />
      {products.map(product => (
        <ProductCard
          key={product.id}
          product={product}
          onDelete={handleDelete}
        />
      ))}
    </div>
  );
}

何時使用?

  • 傳給 memo 元件的 callback function
  • 作為其他 Hook 的 dependency

效能分析工具

React DevTools Profiler

import { Profiler } from 'react';

function onRender(id, phase, actualDuration) {
  console.log(`${id} ${phase}: ${actualDuration}ms`);
}

<Profiler id="ProductList" onRender={onRender}>
  <ProductList />
</Profiler>

常見誤區

不要過度優化

// ❌ 不需要 - 簡單計算不用 useMemo
const fullName = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);

// ✅ 直接計算
const fullName = `${firstName} ${lastName}`;

根據我的經驗,大部分效能問題都來自不必要的重新渲染和大量 DOM 操作。先用 Profiler 找到瓶頸,再針對性地優化,效果最好。

延伸閱讀

如果你需要類似的系統,歡迎聯繫討論。

#React #效能優化
Thousand Lab

Thousand Lab