react-performance-optimizer について
react-performance-optimizerは、レンダリング、リスト、バンドル、メモリの性能問題を調べるReact開発者向けスキルです。まずプロファイラーで原因を特定し、仮想化やコード分割、メモ化などを選ぶ方法を示します。
主な機能
- 01Profilerによる遅い描画の特定
- 02大規模リストの仮想化
- 03ルート・コンポーネントのコード分割
- 04メモ化の適切な適用
- 05バンドル分析とメモリ問題の調査
活用シーン
- 01コンポーネントの再描画やカクつきの診断
- 02大規模リストのスクロール改善
- 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.jsonreferences/bundle-optimization.mdmemory-leaks.mdprofiling-guide.mdscripts/bundle_analyzer.shperformance_audit.ts詳細
React パフォーマンスオプティマイザー
React のパフォーマンス問題の診断と修正に精通し、滑らかで快適な 60fps の体験を実現します。
使用する場面
✅ 次の場合に使用します:
- コンポーネントの再レンダーが遅い
- 大きなリスト(>100 項目)による遅延
- バンドルサイズが >500KB(gzip 圧縮後)
- インタラクティブになるまでの時間が >3 秒
- スクロールやアニメーションがカクつく
- アンマウントされたコンポーネントによるメモリーリーク
❌ 次の場合には使用しません:
- コンポーネントが <10 個のアプリ(時期尚早な最適化)
- バックエンド API の遅延(API を修正してください)
- ネットワーク遅延(キャッシュ/CDN を使用してください)
- React 以外のフレームワーク(フレームワーク固有のツールを使用してください)
クイック判断ツリー
Is your React app slow?
├── Profiler shows >16ms renders? → Use memoization
├── Lists with >100 items? → Use virtualization
├── Bundle size >500KB? → Code splitting
├── Lighthouse score <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>;
}
経験則:次の場合にのみメモ化してください:
- 計算に >5ms かかる(Profiler で計測します)
- 結果が依存配列で使用される
- 子コンポーネントの再レンダーを防ぐ
アンチパターン 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 (>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 >90
□ Time to Interactive <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 のパフォーマンス最適化|メモ化|仮想化|コード分割|バンドルの最適化|プロファイリング