mcpField
R
react-performance-optimizer

react-performance-optimizer

github.com/curiositech開発257203 回インストール2 日前に更新
リポジトリ

react-performance-optimizer について

react-performance-optimizerは、レンダリング、リスト、バンドル、メモリの性能問題を調べるReact開発者向けスキルです。まずプロファイラーで原因を特定し、仮想化やコード分割、メモ化などを選ぶ方法を示します。

主な機能

  1. 01Profilerによる遅い描画の特定
  2. 02大規模リストの仮想化
  3. 03ルート・コンポーネントのコード分割
  4. 04メモ化の適切な適用
  5. 05バンドル分析とメモリ問題の調査

活用シーン

  1. 01コンポーネントの再描画やカクつきの診断
  2. 02大規模リストのスクロール改善
  3. 03起動時に読み込むJavaScriptの削減

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/curiositech/some_claude_skills --skill react-performance-optimizer

インストール後、Claude Code で/react-performance-optimizerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.claude-plugin/
plugin.json
references/
bundle-optimization.md
memory-leaks.md
profiling-guide.md
scripts/
bundle_analyzer.sh
performance_audit.ts

詳細

React パフォーマンスオプティマイザー

React のパフォーマンス問題の診断と修正に精通し、滑らかで快適な 60fps の体験を実現します。

使用する場面

✅ 次の場合に使用します:

  • コンポーネントの再レンダーが遅い
  • 大きなリスト(>100 項目)による遅延
  • バンドルサイズが >500KB(gzip 圧縮後)
  • インタラクティブになるまでの時間が >3 秒
  • スクロールやアニメーションがカクつく
  • アンマウントされたコンポーネントによるメモリーリーク

❌ 次の場合には使用しません:

  • コンポーネントが <10 個のアプリ(時期尚早な最適化)
  • バックエンド API の遅延(API を修正してください)
  • ネットワーク遅延(キャッシュ/CDN を使用してください)
  • React 以外のフレームワーク(フレームワーク固有のツールを使用してください)

クイック判断ツリー

Is your React app slow?
├── Profiler shows &gt;16ms renders? → Use memoization
├── Lists with &gt;100 items? → Use virtualization
├── Bundle size &gt;500KB? → Code splitting
├── Lighthouse score &lt;70? → Multiple optimizations
└── Feels fast enough? → Don't optimize yet

技術の選択

パフォーマンスツール(2024)
ツール目的使用する場面
React DevTools Profiler遅いコンポーネントを見つける常にここから始めます
Lighthouse全体的なパフォーマンススコア比較の前後
webpack-bundle-analyzer大きな依存関係を特定するバンドルが >500KB の場合
why-did-you-render不要な再レンダー再レンダーの多発をデバッグする
React Compiler (2024+)自動メモ化React 19+

タイムライン:

  • 2018:React.memo、useMemo、useCallback を導入
  • 2020:Concurrent Mode(現在の Concurrent Rendering)
  • 2022:React 18 で自動バッチ処理を導入
  • 2024:React Compiler(自動最適化)
  • 2025+:React Compiler が手動メモ化に取って代わると予想

よくあるアンチパターン

アンチパターン 1:時期尚早なメモ化

初心者の考え方:「高速化のためにすべてを useMemo で囲む」

問題:わずかな効果しかないのに、複雑さとオーバーヘッドが増します。

誤った方法:

// ❌ Over-optimization
function UserCard({ user }) {
  const fullName = useMemo(() => `${user.first} ${user.last}`, [user]);
  const age = useMemo(() => new Date().getFullYear() - user.birthYear, [user]);

  return <div>{fullName}, {age}</div>;
}

誤りである理由:文字列の連結は useMemo のオーバーヘッドより高速です。

正しい方法:

// ✅ Simple is fast
function UserCard({ user }) {
  const fullName = `${user.first} ${user.last}`;
  const age = new Date().getFullYear() - user.birthYear;

  return <div>{fullName}, {age}</div>;
}

経験則:次の場合にのみメモ化してください:

  1. 計算に >5ms かかる(Profiler で計測します)
  2. 結果が依存配列で使用される
  3. 子コンポーネントの再レンダーを防ぐ

アンチパターン 2:コールバックをメモ化しない

問題:レンダーのたびに新しい関数インスタンスが作られ、React.memo が機能しなくなります。

誤った方法:

// ❌ Child re-renders on every parent render
function Parent() {
  const [count, setCount] = useState(0);

  return (
    <Child onUpdate={() => setCount(count + 1)} />
  );
}

const Child = React.memo(({ onUpdate }) => {
  return <button onClick={onUpdate}>Update</button>;
});

誤りである理由:アロー関数が新しい参照を作成するため、React.memo が役に立ちません。

正しい方法:

// ✅ Stable callback reference
function Parent() {
  const [count, setCount] = useState(0);

  const handleUpdate = useCallback(() => {
    setCount(c => c + 1);  // Updater function avoids dependency
  }, []);

  return <Child onUpdate={handleUpdate} />;
}

const Child = React.memo(({ onUpdate }) => {
  return <button onClick={onUpdate}>Update</button>;
});

アンチパターン 3:仮想化せずに大きなリストをレンダーする

問題:1000 個以上の DOM ノードをレンダーすると遅延が発生します。

症状:スクロールがカクつき、初回レンダーが遅くなります。

誤った方法:

// ❌ Renders all 10,000 items
function UserList({ users }) {
  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

正しい方法:

// ✅ Only renders visible items
import { FixedSizeList } from 'react-window';

function UserList({ users }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={users.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <UserCard user={users[index]} />
        </div>
      )}
    </FixedSizeList>
  );
}

効果:10,000 項目の場合、レンダー時間が 5 秒から 50ms に短縮されます。


アンチパターン 4:コード分割をしない

問題:2MB のバンドルを最初にダウンロードするため、初期読み込みが遅くなります。

誤った方法:

// ❌ Everything in main bundle
import AdminPanel from './AdminPanel';  // 500KB
import Dashboard from './Dashboard';
import Settings from './Settings';

function App() {
  return (
    <Routes>
      <Route path="/admin" element={<AdminPanel />} />
      <Route path="/dashboard" element={<Dashboard />} />
      <Route path="/settings" element={<Settings />} />
    </Routes>
  );
}

正しい方法:

// ✅ Lazy load routes
import { lazy, Suspense } from 'react';

const AdminPanel = lazy(() => import('./AdminPanel'));
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/admin" element={<AdminPanel />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

効果:初期バンドルが 2MB から 300KB に減少します。


アンチパターン 5:レンダー中に負荷の高い処理を行う

問題:レンダーのたびに重い計算が実行されます。

誤った方法:

// ❌ Sorts on every render (even when data unchanged)
function ProductList({ products }) {
  const sorted = products.sort((a, b) => b.price - a.price);

  return <div>{sorted.map(p => <Product product={p} />)}</div>;
}

正しい方法:

// ✅ Memoize expensive operation
function ProductList({ products }) {
  const sorted = useMemo(
    () => [...products].sort((a, b) => b.price - a.price),
    [products]
  );

  return <div>{sorted.map(p => <Product product={p} />)}</div>;
}

実装パターン

パターン 1:純粋なコンポーネントに React.memo を使用する
// Prevent re-render when props unchanged
const ExpensiveComponent = React.memo(({ data }) => {
  // Complex rendering logic
  return <div>{/* ... */}</div>;
});

// With custom comparison
const UserCard = React.memo(
  ({ user }) => <div>{user.name}</div>,
  (prevProps, nextProps) => {
    // Return true if props equal (skip re-render)
    return prevProps.user.id === nextProps.user.id;
  }
);
パターン 2:負荷の高い計算に useMemo を使用する
function DataTable({ rows, columns }) {
  const sortedAndFiltered = useMemo(() => {
    console.log('Recomputing...');  // Only logs when rows/columns change

    return rows
      .filter(row => row.visible)
      .sort((a, b) => a.timestamp - b.timestamp);
  }, [rows, columns]);

  return <Table data={sortedAndFiltered} />;
}
パターン 3:安定した参照に useCallback を使用する
function SearchBox({ onSearch }) {
  const [query, setQuery] = useState('');

  // Stable reference, doesn't break child memoization
  const handleSubmit = useCallback(() => {
    onSearch(query);
  }, [query, onSearch]);

  return (
    <form onSubmit={handleSubmit}>
      <input value={query} onChange={e => setQuery(e.target.value)} />
    </form>
  );
}
パターン 4:仮想化(react-window)
import { VariableSizeList } from 'react-window';

function MessageList({ messages }) {
  const getItemSize = (index) => {
    // Dynamic heights based on content
    return messages[index].text.length > 100 ? 80 : 50;
  };

  return (
    <VariableSizeList
      height={600}
      itemCount={messages.length}
      itemSize={getItemSize}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <Message message={messages[index]} />
        </div>
      )}
    </VariableSizeList>
  );
}
パターン 5:React.lazy によるコード分割
// Route-based splitting
const routes = [
  { path: '/home', component: lazy(() => import('./Home')) },
  { path: '/about', component: lazy(() => import('./About')) },
  { path: '/contact', component: lazy(() => import('./Contact')) }
];

// Component-based splitting
const HeavyChart = lazy(() => import('./HeavyChart'));

function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>Show Chart</button>

      {showChart && (
        <Suspense fallback={<Spinner />}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
}

本番環境向けチェックリスト

□ Profiler analysis completed (identified slow components)
□ Large lists use virtualization (&gt;100 items)
□ Routes code-split with React.lazy
□ Heavy components lazy-loaded
□ Callbacks memoized with useCallback
□ Expensive computations use useMemo
□ Pure components wrapped in React.memo
□ Bundle analyzed (no duplicate dependencies)
□ Tree-shaking enabled (ESM imports)
□ Images optimized and lazy-loaded
□ Lighthouse score &gt;90
□ Time to Interactive &lt;3 seconds

使用する場合と避ける場合

シナリオ最適化するか
1000 件以上のリスト項目をレンダーする✅ はい - 仮想化します
大きな配列をソート/フィルタリングする✅ はい - useMemo を使用します
メモ化された子コンポーネントにコールバックを渡す✅ はい - useCallback を使用します
文字列の連結❌ いいえ - 十分高速です
単純な算術演算❌ いいえ - メモ化しないでください
10 項目のリスト❌ いいえ - 時期尚早な最適化です

参考資料

  • /references/profiling-guide.md - React DevTools Profiler の使い方
  • /references/bundle-optimization.md - バンドルサイズを削減する方法
  • /references/memory-leaks.md - メモリーリークの検出と修正

スクリプト

  • scripts/performance_audit.ts - パフォーマンスの自動チェック
  • scripts/bundle_analyzer.sh - バンドルの分析と可視化

このスキルの対象:React のパフォーマンス最適化|メモ化|仮想化|コード分割|バンドルの最適化|プロファイリング

コメント