jpskill.com
🛠️ 開発・MCP コミュニティ

react-performance

Performance optimization for React web applications. Use when optimizing renders, implementing virtualization, memoizing components, or debugging performance issues.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o react-performance.zip https://jpskill.com/download/17851.zip && unzip -o react-performance.zip && rm react-performance.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17851.zip -OutFile "$d\react-performance.zip"; Expand-Archive "$d\react-performance.zip" -DestinationPath $d -Force; ri "$d\react-performance.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して react-performance.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → react-performance フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

React のパフォーマンス (Web)

問題提起

React のパフォーマンスに関する問題は、不要な再レンダリング、最適化されていないリスト、およびメインスレッドでの高コストな計算に起因することがよくあります。React のレンダリングの振る舞いを理解することが、パフォーマンスの高いアプリケーションを構築するための鍵となります。


パターン: メモ化

useMemo - 高コストな計算

// ✅ 正しい: 高コストな計算をメモ化する
const sortedAndFilteredItems = useMemo(() => {
  return items
    .filter(item => item.active)
    .sort((a, b) => b.score - a.score)
    .slice(0, 100);
}, [items]);

// ❌ 間違い: レンダリングごとに再計算する
const sortedAndFilteredItems = items
  .filter(item => item.active)
  .sort((a, b) => b.score - a.score);

// ❌ 間違い: 単純なアクセスをメモ化する (オーバーヘッド > メリット)
const userName = useMemo(() => user.name, [user.name]);

useMemo を使用するべき時:

  • 配列の変換 (filter, sort, map チェーン)
  • メモ化された子要素に渡されるオブジェクトの生成
  • O(n) 以上の複雑さを持つ計算

useCallback - 安定した関数参照

// ✅ 正しい: 子要素の props のための安定したコールバック
const handleClick = useCallback((id: string) => {
  setSelectedId(id);
}, []);

// メモ化された子要素に渡す
<MemoizedItem onClick={handleClick} />

// ❌ 間違い: 不安定な依存関係を持つ useCallback
const handleClick = useCallback((id: string) => {
  doSomething(unstableObject); // unstableObject はレンダリングごとに変化する
}, [unstableObject]); // 意味がない

useCallback を使用するべき時:

  • メモ化された子要素に渡されるコールバック
  • 依存関係配列内のコールバック
  • 子要素の再レンダリングを引き起こすイベントハンドラ

パターン: React.memo

// 安定した props を受け取るコンポーネントをラップする
const ItemCard = memo(function ItemCard({
  item,
  onSelect
}: Props) {
  return (
    <div onClick={() => onSelect(item.id)}>
      <h3>{item.name}</h3>
      <p>{item.price}</p>
    </div>
  );
});

// 複雑な props のためのカスタム比較
const ItemCard = memo(
  function ItemCard({ item, onSelect }: Props) {
    // ...
  },
  (prevProps, nextProps) => {
    // props が等しい場合は true を返す (再レンダリングをスキップする)
    return (
      prevProps.item.id === nextProps.item.id &&
      prevProps.item.price === nextProps.item.price
    );
  }
);

React.memo を使用するべき時:

  • リストアイテムコンポーネント
  • 安定したプリミティブな props を受け取るコンポーネント
  • 頻繁にレンダリングされるが、ほとんど変更されないコンポーネント

使用すべきでない時:

  • 常に新しい props を受け取るコンポーネント
  • 単純なコンポーネント (オーバーヘッド > メリット)
  • ルートレベルのページ

パターン: リストの仮想化

長いリストの場合、react-window または react-virtualized を使用して、表示されているアイテムのみをレンダリングします。

import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }: { items: Item[] }) {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <ItemCard item={items[index]} />
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      width="100%"
      itemCount={items.length}
      itemSize={80}
    >
      {Row}
    </FixedSizeList>
  );
}

// 可変の高さのアイテム
import { VariableSizeList } from 'react-window';

function VariableList({ items }: { items: Item[] }) {
  const getItemSize = (index: number) => {
    return items[index].expanded ? 200 : 80;
  };

  return (
    <VariableSizeList
      height={600}
      width="100%"
      itemCount={items.length}
      itemSize={getItemSize}
    >
      {Row}
    </VariableSizeList>
  );
}

仮想化するべき時:

  • 100 以上のアイテムを持つリスト
  • 複雑なアイテムコンポーネント
  • 多数の子要素を持つスクロール可能なコンテナ

パターン: Zustand セレクターの最適化

問題: ストア全体を選択すると、状態が変更されるたびに再レンダリングが発生します。

// ❌ 間違い: ストアの変更時に再レンダリングが発生する
const store = useAppStore();
// または
const { items, loading, filters, ... } = useAppStore();

// ✅ 正しい: 選択された値が変更された場合にのみ再レンダリングが発生する
const items = useAppStore((s) => s.items);
const loading = useAppStore((s) => s.loading);

// ✅ 正しい: 浅い比較による複数の値
import { useShallow } from 'zustand/react/shallow';

const { items, loading } = useAppStore(
  useShallow((s) => ({
    items: s.items,
    loading: s.loading
  }))
);

パターン: 再レンダリングの回避

オブジェクト/配列の安定性

// ❌ 間違い: レンダリングごとに新しいオブジェクト
<ChildComponent style={{ padding: 10 }} />
<ChildComponent config={{ enabled: true }} />

// ✅ 正しい: 安定した参照
const style = useMemo(() => ({ padding: 10 }), []);
const config = useMemo(() => ({ enabled: true }), []);

<ChildComponent style={style} />
<ChildComponent config={config} />

// ✅ 正しい: またはコンポーネントの外で定義する
const style = { padding: 10 };

function Parent() {
  return <ChildComponent style={style} />;
}

子要素の安定性

// ❌ 間違い: インライン関数はレンダリングごとに新しい要素を作成する
<Parent>
  {() => <Child />}
</Parent>

// ✅ 正しい: 安定した要素
const child = useMemo(() => <Child />, [deps]);
<Parent>{child}</Parent>

パターン: コード分割

import { lazy, Suspense } from 'react';

// コンポーネントを遅延ロードする
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

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

// 名前付きエクスポート
const Dashboard = lazy(() =>
  import('./pages/Dashboard').then(module => ({
    default: module.Dashboard
  }))
);

パターン: デバウンスとスロットリング


import { useMemo } from 'react';
import { debounce, throttle } from 'lodash-es';

// デバウンス - ユーザーの入力が止まるまで待つ
function SearchInput({ onSearch }: { onSearch: (query: string) => void }) {
  const debouncedSearch = useMemo(
    () => debounce(onSearch,
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

React Performance (Web)

Problem Statement

React performance issues often stem from unnecessary re-renders, unoptimized lists, and expensive computations on the main thread. Understanding React's rendering behavior is key to building performant applications.


Pattern: Memoization

useMemo - Expensive Computations

// ✅ CORRECT: Memoize expensive calculation
const sortedAndFilteredItems = useMemo(() => {
  return items
    .filter(item => item.active)
    .sort((a, b) => b.score - a.score)
    .slice(0, 100);
}, [items]);

// ❌ WRONG: Recalculates every render
const sortedAndFilteredItems = items
  .filter(item => item.active)
  .sort((a, b) => b.score - a.score);

// ❌ WRONG: Memoizing simple access (overhead > benefit)
const userName = useMemo(() => user.name, [user.name]);

When to use useMemo:

  • Array transformations (filter, sort, map chains)
  • Object creation passed to memoized children
  • Computations with O(n) or higher complexity

useCallback - Stable Function References

// ✅ CORRECT: Stable callback for child props
const handleClick = useCallback((id: string) => {
  setSelectedId(id);
}, []);

// Pass to memoized child
<MemoizedItem onClick={handleClick} />

// ❌ WRONG: useCallback with unstable deps
const handleClick = useCallback((id: string) => {
  doSomething(unstableObject); // unstableObject changes every render
}, [unstableObject]); // Defeats the purpose

When to use useCallback:

  • Callbacks passed to memoized children
  • Callbacks in dependency arrays
  • Event handlers that would cause child re-renders

Pattern: React.memo

// Wrap components that receive stable props
const ItemCard = memo(function ItemCard({
  item,
  onSelect
}: Props) {
  return (
    <div onClick={() => onSelect(item.id)}>
      <h3>{item.name}</h3>
      <p>{item.price}</p>
    </div>
  );
});

// Custom comparison for complex props
const ItemCard = memo(
  function ItemCard({ item, onSelect }: Props) {
    // ...
  },
  (prevProps, nextProps) => {
    // Return true if props are equal (skip re-render)
    return (
      prevProps.item.id === nextProps.item.id &&
      prevProps.item.price === nextProps.item.price
    );
  }
);

When to use React.memo:

  • List item components
  • Components receiving stable primitive props
  • Components that render frequently but rarely change

When NOT to use:

  • Components that always receive new props
  • Simple components (overhead > benefit)
  • Root-level pages

Pattern: List Virtualization

For long lists, render only visible items using react-window or react-virtualized.

import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }: { items: Item[] }) {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <ItemCard item={items[index]} />
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      width="100%"
      itemCount={items.length}
      itemSize={80}
    >
      {Row}
    </FixedSizeList>
  );
}

// Variable height items
import { VariableSizeList } from 'react-window';

function VariableList({ items }: { items: Item[] }) {
  const getItemSize = (index: number) => {
    return items[index].expanded ? 200 : 80;
  };

  return (
    <VariableSizeList
      height={600}
      width="100%"
      itemCount={items.length}
      itemSize={getItemSize}
    >
      {Row}
    </VariableSizeList>
  );
}

When to virtualize:

  • Lists with 100+ items
  • Complex item components
  • Scrollable containers with many children

Pattern: Zustand Selector Optimization

Problem: Selecting entire store causes re-render on any state change.

// ❌ WRONG: Re-renders on ANY store change
const store = useAppStore();
// or
const { items, loading, filters, ... } = useAppStore();

// ✅ CORRECT: Only re-renders when selected values change
const items = useAppStore((s) => s.items);
const loading = useAppStore((s) => s.loading);

// ✅ CORRECT: Multiple values with shallow comparison
import { useShallow } from 'zustand/react/shallow';

const { items, loading } = useAppStore(
  useShallow((s) => ({
    items: s.items,
    loading: s.loading
  }))
);

Pattern: Avoiding Re-Renders

Object/Array Stability

// ❌ WRONG: New object every render
<ChildComponent style={{ padding: 10 }} />
<ChildComponent config={{ enabled: true }} />

// ✅ CORRECT: Stable reference
const style = useMemo(() => ({ padding: 10 }), []);
const config = useMemo(() => ({ enabled: true }), []);

<ChildComponent style={style} />
<ChildComponent config={config} />

// ✅ CORRECT: Or define outside component
const style = { padding: 10 };

function Parent() {
  return <ChildComponent style={style} />;
}

Children Stability

// ❌ WRONG: Inline function creates new element each render
<Parent>
  {() => <Child />}
</Parent>

// ✅ CORRECT: Stable element
const child = useMemo(() => <Child />, [deps]);
<Parent>{child}</Parent>

Pattern: Code Splitting

import { lazy, Suspense } from 'react';

// Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

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

// Named exports
const Dashboard = lazy(() =>
  import('./pages/Dashboard').then(module => ({
    default: module.Dashboard
  }))
);

Pattern: Debouncing and Throttling

import { useMemo } from 'react';
import { debounce, throttle } from 'lodash-es';

// Debounce - wait until user stops typing
function SearchInput({ onSearch }: { onSearch: (query: string) => void }) {
  const debouncedSearch = useMemo(
    () => debounce(onSearch, 300),
    [onSearch]
  );

  return (
    <input
      type="text"
      onChange={(e) => debouncedSearch(e.target.value)}
    />
  );
}

// Throttle - limit how often function runs
function InfiniteScroll({ onLoadMore }: { onLoadMore: () => void }) {
  const throttledLoad = useMemo(
    () => throttle(onLoadMore, 1000),
    [onLoadMore]
  );

  useEffect(() => {
    const handleScroll = () => {
      if (nearBottom()) {
        throttledLoad();
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [throttledLoad]);

  return <div>...</div>;
}

Pattern: Image Optimization

// Lazy load images
<img
  src={imageUrl}
  loading="lazy"
  alt="Description"
/>

// With intersection observer for more control
function LazyImage({ src, alt }: { src: string; alt: string }) {
  const [isVisible, setIsVisible] = useState(false);
  const imgRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { rootMargin: '100px' }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <div ref={imgRef}>
      {isVisible ? (
        <img src={src} alt={alt} />
      ) : (
        <div className="placeholder" />
      )}
    </div>
  );
}

// Next.js Image component (if using Next.js)
import Image from 'next/image';

<Image
  src={imageUrl}
  alt="Description"
  width={400}
  height={300}
  placeholder="blur"
  blurDataURL={blurHash}
/>

Pattern: Web Workers for Heavy Computation

// worker.ts
self.onmessage = (e: MessageEvent<{ data: number[] }>) => {
  const result = heavyComputation(e.data.data);
  self.postMessage(result);
};

// Component
function DataProcessor({ data }: { data: number[] }) {
  const [result, setResult] = useState(null);

  useEffect(() => {
    const worker = new Worker(new URL('./worker.ts', import.meta.url));

    worker.onmessage = (e) => {
      setResult(e.data);
    };

    worker.postMessage({ data });

    return () => worker.terminate();
  }, [data]);

  return result ? <Results data={result} /> : <Loading />;
}

Pattern: Detecting Re-Renders

React DevTools Profiler

  1. Open React DevTools
  2. Go to Profiler tab
  3. Click record, interact, stop
  4. Review "Flamegraph" for render times
  5. Look for components rendering unnecessarily

why-did-you-render

// Setup in development
import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  });
}

// Mark specific component for tracking
ItemCard.whyDidYouRender = true;

Console Logging

// Quick check for re-renders
function ItemCard({ item }: Props) {
  console.log('ItemCard render:', item.id);
  // ...
}

Performance Checklist

Before shipping:

  • [ ] Large lists are virtualized
  • [ ] List items are memoized with React.memo
  • [ ] Callbacks passed to items use useCallback
  • [ ] Zustand selectors are specific (not whole store)
  • [ ] Images use lazy loading
  • [ ] Heavy routes are code-split
  • [ ] No inline object/function props to memoized children
  • [ ] Profiler shows no unnecessary re-renders

Common Issues

Issue Solution
List scroll lag Virtualize list, memoize items
Component re-renders too often Check selector specificity, memoize props
Slow initial render Code split, reduce bundle size
Memory growing Check for event listener cleanup, state accumulation
UI freezes on interaction Move computation to web worker or defer

Relationship to Other Skills

  • react-zustand-patterns: Selector optimization patterns
  • react-async-patterns: Proper async handling prevents re-render loops