react-best-practices について
react-best-practicesは、保守しやすくテスト可能なアプリを作るためのReact向けスキルです。React 18/19のパターン、コンポーネント設計、テスト、計測に基づく最適化を扱います。
主な機能
- 01サーバー/クライアントコンポーネントの選択
- 02フックとコンポーネント合成パターン
- 03エラーバウンダリーとSuspense
- 04コンテキストと描画の最適化
- 05アクセシビリティを考慮した動作テスト
活用シーン
- 01コンポーネントのデータと状態の境界設定
- 02再利用可能なReactコンポーネントの設計
- 03読み込み・エラー・操作状態のテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pixel-process-ug/ superkit-agents --skill react-best-practicesインストール後、Claude Code で/react-best-practicesを入力して読み込まれたか確認します。
詳細
React のベストプラクティス
概要
最新の React パターンを適用し、保守性、パフォーマンス、テスト性に優れたアプリケーションを構築します。このスキルでは、Server Components、フックのベストプラクティス、コンポーネントのコンポジション、エラーバウンダリ、Suspense、コンテキストの最適化、レンダリングパフォーマンスなど、React 18/19 の機能を扱います。React に特化した詳しい内容を提供し、senior-frontend スキルを補完します。
開始時に宣言:「React 固有のパターンに react-best-practices スキルを使用します。」
フェーズ 1:コンポーネントの要件を分析する
目標: コーディングを始める前に、コンポーネントの責務とデータ要件を理解します。
アクション
- コンポーネントの単一の責務を特定する
- データ要件を判断する(サーバーデータかクライアントデータか)
- Server Component(デフォルト)か Client Component を選択する
- 状態管理の要件を特定する
- エラー状態と読み込み状態を計画する
サーバーとクライアントの判断表
| 要件 | コンポーネントの種類 | 理由 |
|---|---|---|
| 直接のデータ取得(DB、API) | Server(デフォルト) | クライアント JS が不要で、より高速 |
| イベントハンドラー(onClick、onChange) | Client('use client') | ブラウザーでのインタラクティブ性が必要 |
| useState / useReducer | Client | 状態にはクライアントランタイムが必要 |
| useEffect / useLayoutEffect | Client | 副作用にはクライアントが必要 |
| ブラウザー API(window、localStorage) | Client | サーバーにはブラウザーがない |
| クライアント機能を使用するサードパーティーライブラリ | Client | ライブラリにクライアントが必要 |
| インタラクティブ性が不要 | Server(デフォルト) | バンドルが小さくなり、より高速 |
停止 — 次の項目を満たすまでフェーズ 2 に進まないでください:
- コンポーネントの責務(単一の目的)が定義されている
- 理由を明確にして Server と Client のどちらにするか決定している
- データ要件が整理されている
フェーズ 2:適切なパターンで実装する
目標: コンポーネントの要件に適した React パターンを適用します。
アクション
- 適切なコンポジションパターンを適用する
- フックを正しく実装する
- エラーバウンダリと Suspense を追加する
- プロファイリングで必要性が確認された場合に、レンダリングを最適化する
- 動作を検証するテストを書く
停止 — 次の項目を満たすまでフェーズ 3 に進まないでください:
- パターンがコンポーネントの実際の要件に合っている
- 不要な複雑さがない(早すぎる最適化をしていない)
- ユーザーに見える動作をテストでカバーしている
フェーズ 3:テストと検証を行う
目標: テストを通じてコンポーネントの動作を検証します。
アクション
- アクセシブルなクエリを使ってテストを書く
- ユーザーの操作とその結果をテストする
- エラー状態と読み込み状態をテストする
- アクセシビリティを検証する
クエリの優先順位(React Testing Library)
| 優先順位 | クエリ | 用途 |
|---|---|---|
| 1 番目 | getByRole | ARIA ロールを持つすべての要素 |
| 2 番目 | getByLabelText | フォームフィールド |
| 3 番目 | getByPlaceholderText | ラベルのないフィールド |
| 4 番目 | getByText | インタラクティブでない要素 |
| 最後 | getByTestId | ほかの方法が使えない場合 |
停止 — 次の項目を満たしたらテスト完了です:
- ユーザーの操作によって期待どおりの結果が得られる
- エラー状態がテストされている
- アクセシビリティチェックに合格している
フックのベストプラクティス
useState
// Functional updates for state based on previous state
setCount(prev => prev + 1);
// Lazy initialization for expensive initial values
const [data, setData] = useState(() => computeExpensiveInitialValue());
// Group related state
const [form, setForm] = useState({ name: '', email: '', role: 'user' });
useEffect
依存配列のルール
- 時間の経過とともに変化するコンポーネントスコープ内の値をすべて含める
- エフェクト内で使う関数は、エフェクト内で定義するか、useCallback でラップする
- 依存関係を偽ってはいけない(ESLint:
react-hooks/exhaustive-deps)
クリーンアップのパターン
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const res = await fetch(url, { signal: controller.signal });
const data = await res.json();
setData(data);
} catch (e) {
if (e.name !== 'AbortError') setError(e);
}
}
fetchData();
return () => controller.abort();
}, [url]);
useEffect を使用しない場合
| useEffect の代わりに使う用途 | 使用するもの |
|---|---|
| データの取得 | React Query、SWR、または Server Components |
| データの変換 | レンダリング中に計算する |
| ユーザーイベント | イベントハンドラー |
| 外部ストアとの同期 | useSyncExternalStore |
カスタムフックのルール
- 名前は
useで始める - 再利用可能な状態付きロジックをカプセル化する
- 1 つの関心事につき 1 つのフックを作る
- 値が 2 つを超える場合は、配列ではなくオブジェクトを返す
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
コンポーネントのコンポジションパターン
複合コンポーネント
function Tabs({ children, defaultValue }: TabsProps) {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div role="tablist">{children}</div>
</TabsContext.Provider>
);
}
Tabs.Tab = function Tab({ value, children }: TabProps) {
const { activeTab, setActiveTab } = useTabsContext();
return (
<button role="tab" aria-selected={activeTab === value} onClick={() => setActiveTab(value)}>
{children}
</button>
);
};
Tabs.Panel = function Panel({ value, children }: PanelProps) {
const { activeTab } = useTabsContext();
if (activeTab !== value) return null;
return <div role="tabpanel">{children}</div>;
};
コンポジションの判断表
| パターン | 使用する場面 | 例 |
|---|---|---|
| 複合コンポーネント | 関連するコンポーネントが暗黙的な状態を共有する場合 | タブ、アコーディオン、メニュー |
| スロット(子要素) | 複雑なコンテンツレイアウトの場合 | ヘッダー/本文/フッターを備えたカード |
| レンダープロップ | 柔軟なレンダリングのために子要素が親のデータを必要とする場合 | カスタムレンダーを備えた DataFetcher |
| 高階コンポーネント | 横断的な関心事(レガシー)の場合 | withAuth、withTheme |
| カスタムフック | UI を伴わない、再利用可能な状態管理ロジックの場合 | useDebounce、useLocalStorage |
スロットパターン
// Prefer composition over props for complex content
// Bad
<Card title="Hello" subtitle="World" icon={<Star />} actions={<Button>Edit</Button>} />
// Good
<Card>
<Card.Header>
<Card.Icon><Star /></Card.Icon>
<Card.Title>Hello</Card.Title>
</Card.Header>
<Card.Actions>
<Button>Edit</Button>
</Card.Actions>
</Card>
エラーバウンダリー
配置戦略の判断表
| レベル | 目的 | 例 |
|---|---|---|
| ルートレベル | ページレベルのクラッシュを捕捉する | Next.js の error.tsx |
| 機能レベル | 機能の障害を分離する | 各主要セクションをラップする |
| データレベル | 非同期データコンポーネントをラップする | Suspense 境界の周囲 |
| リーフレベルには配置しない | 粒度が細かすぎて、ノイズが増える | 個々のボタンはラップしない |
Suspense
// Nested Suspense for granular loading
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</Suspense>
Context の最適化
問題:Context が不要な再レンダリングを引き起こす
解決策の判断表
| 手法 | 使用する場面 | 例 |
|---|---|---|
| 更新頻度ごとに Context を分割する | 頻繁に更新される値と、まれに更新される値がある場合 | ThemeContext(まれ)と UIStateContext(頻繁) |
| Context の値をメモ化する | 同じデータで Provider が再レンダリングされる場合 | useMemo(() => ({ state, dispatch }), [state]) |
| セレクターを使用する(Zustand/Jotai) | 細粒度のサブスクリプションが必要な場合 | useStore(state => state.user.name) |
| 状態を上位に持ち上げる | 親だけを再レンダリングする必要がある場合 | データをメモ化した子要素に props として渡す |
レンダリングの最適化
メモ化の判断表
| 手法 | 使用する場面 | 使用しない場面 |
|---|---|---|
React.memo | 同じ props で頻繁にレンダリングされ、かつ再レンダリングのコストが高い場合 | props がレンダリングのたびに変わる場合 |
useMemo | 計算コストが高い場合、または依存値の参照同一性が必要な場合 | 単純な計算の場合 |
useCallback | メモ化された子要素のために関数参照を安定させる場合 | 関数を props として渡さない場合 |
| なし(デフォルト) | 常にここから始める | 時期尚早な最適化 |
ルール: メモ化する前にプロファイリングしてください。時期尚早なメモ化は、React で最もよくあるアンチパターンです。
仮想化
100 件を超えるリストの場合:
import { useVirtualizer } from '@tanstack/react-virtual';
サーバーコンポーネントのルール
- フックは使用できません
- ブラウザー API は使用できません
- Client Components に props として関数を渡すことはできません
- Client Components をインポートしてレンダリングすることはできます
- シリアライズ可能なデータを Client Components に渡すことはできます
// Server Component — fetches data directly
async function UserProfile({ userId }: { userId: string }) {
const user = await db.user.findUnique({ where: { id: userId } });
return (
<div>
<h1>{user.name}</h1>
<UserActions userId={userId} /> {/* Client Component child */}
</div>
);
}
// Client Component — handles interactivity
'use client';
function UserActions({ userId }: { userId: string }) {
const [isFollowing, setIsFollowing] = useState(false);
return <Button onClick={() => toggleFollow(userId)}>Follow</Button>;
}
アンチパターン/よくある間違い
| アンチパターン | 問題点 | 正しい方法 |
|---|---|---|
データ取得に useEffect を使用する | 競合状態が発生し、キャッシュも重複排除もできない | React Query または Server Components を使用する |
| 2 レベルを超える props のバケツリレー | 密結合になり、保守が困難になる | コンポジション、Context、または Zustand を使用する |
| 派生状態を保存する | 計算できる状態は不要な状態である | レンダリング中に計算する |
props から状態を同期するために useEffect を使用する | 不要なエフェクトと古いクロージャーが発生する | レンダリング中に導出するか、key prop を使用する |
| 巨大なコンポーネント(200 行超) | 読みづらく、テストや保守が困難になる | サブコンポーネントを切り出す |
| 動的リストの key にインデックスを使用する | 誤った再調整や古い状態が発生する | 安定した一意の ID を使用する |
| DOM を直接操作する | React の再調整を迂回する | ref は控えめに使い、状態を優先する |
| 状態値を直接テストする | 実装の詳細に依存し、リファクタリングで壊れる | ユーザーに見える結果をテストする |
| すべてをメモ化する | 複雑さが増し、かえって遅くなることが多い | まずプロファイリングし、その後に最適化する |
ドキュメントの検索(Context7)
最新のドキュメントを参照するには、mcp__context7__resolve-library-id、続いて mcp__context7__query-docs を使用してください。取得したドキュメントは、記憶している知識より優先されます。
react— フック、Context、Suspense、サーバーコンポーネント、または React 19 以降の変更についてnext.js— App Router のパターン、データ取得、またはサーバーアクションについて
統合ポイント
| スキル | 関連性 |
|---|---|
senior-frontend | フロントエンドスキルでは、このスキルの React パターンを使用します |
testing-strategy | React のテストでは、戦略ピラミッドに従います |
clean-code | コンポーネントのコードでは、クリーンコードの原則に従います |
performance-optimization | React のレンダリング最適化では、測定方法に従います |
webapp-testing | E2E テストで React コンポーネントの動作を検証します |
code-review | レビューで React のアンチパターンを確認します |
acceptance-testing | UI の受け入れ基準をもとに、コンポーネントのテストを作成します |
スキルの種類
FLEXIBLE — 使用する React のバージョン、プロジェクト構成、チームの慣習に応じて、これらのパターンを適用してください。原則は一貫していますが、実装の詳細は異なる場合があります。最適化の前に、必ずプロファイリングを行ってください。