Reactパフォーマンス最適化:実践的なテクニック集

React
フロントエンド
パフォーマンス
Reactアプリケーションのパフォーマンスを改善するための実践的なテクニックを紹介します。
作者

山田太郎

公開

2024年2月15日

はじめに

Reactアプリケーションが大規模化すると、パフォーマンスの問題が顕在化してきます。 この記事では、実務で使える最適化テクニックを紹介します。

1. 不要な再レンダリングを防ぐ

React.memoの活用

import { memo } from 'react';

interface UserCardProps {
  name: string;
  email: string;
}

// propsが変更されない限り再レンダリングされない
const UserCard = memo(function UserCard({ name, email }: UserCardProps) {
  return (
    <div className="user-card">
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
});

useMemoで計算結果をキャッシュ

import { useMemo } from 'react';

function ExpensiveComponent({ items }: { items: Item[] }) {
  // itemsが変更された時のみ再計算
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => a.price - b.price);
  }, [items]);

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.name}: ¥{item.price}</li>
      ))}
    </ul>
  );
}

useCallbackでコールバックを安定化

import { useCallback, useState } from 'react';

function TodoList() {
  const [todos, setTodos] = useState<Todo[]>([]);

  // 関数の参照が安定する
  const handleDelete = useCallback((id: string) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);

  return (
    <ul>
      {todos.map(todo => (
        <TodoItem 
          key={todo.id} 
          todo={todo} 
          onDelete={handleDelete} 
        />
      ))}
    </ul>
  );
}

2. 遅延読み込み(Lazy Loading)

コンポーネントの遅延読み込み

import { lazy, Suspense } from 'react';

// 動的インポート
const HeavyChart = lazy(() => import('./HeavyChart'));

function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<div>Loading chart...</div>}>
        <HeavyChart />
      </Suspense>
    </div>
  );
}

画像の遅延読み込み

function LazyImage({ src, alt }: { src: string; alt: string }) {
  return (
    <img 
      src={src} 
      alt={alt} 
      loading="lazy"
      decoding="async"
    />
  );
}

3. 仮想化(Virtualization)

大量のリストはreact-window@tanstack/react-virtualで仮想化します。

import { FixedSizeList } from 'react-window';

interface RowProps {
  index: number;
  style: React.CSSProperties;
}

function VirtualList({ items }: { items: string[] }) {
  const Row = ({ index, style }: RowProps) => (
    <div style={style}>
      {items[index]}
    </div>
  );

  return (
    <FixedSizeList
      height={400}
      width={300}
      itemCount={items.length}
      itemSize={35}
    >
      {Row}
    </FixedSizeList>
  );
}

4. 状態管理の最適化

状態の分割

// ❌ 大きな状態オブジェクト
const [state, setState] = useState({
  user: null,
  posts: [],
  comments: [],
  notifications: [],
});

// ✅ 関心ごとに分割
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [notifications, setNotifications] = useState([]);

Contextの分割

// 変更頻度の低いデータと高いデータを分離
const UserContext = createContext(null);      // 変更頻度:低
const NotificationContext = createContext(null); // 変更頻度:高

5. パフォーマンス計測

React DevTools Profiler

React DevToolsのProfilerタブで、レンダリング時間やコミット数を確認できます。

useDebugValue

カスタムフックのデバッグに役立ちます。

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  
  useDebugValue(isOnline ? 'Online' : 'Offline');
  
  // ...
  return isOnline;
}

まとめ

パフォーマンス最適化は、まず計測してから行うことが重要です。 闇雲に最適化すると、かえってコードの複雑さが増すこともあります。

警告注意

早すぎる最適化は避けましょう。まずは動くコードを書き、問題が発生してから最適化を検討してください。

参考リンク