react-expert について
ReactのコードレビューやUI構築を支援する開発者向けスキルです。Hooks、コンポーネント設計、React 19、パフォーマンス、フォーム、テスト、アクセシビリティを扱います。
主な機能
- 01Reactコードとパターンのレビュー
- 02React 19 APIとActionsの活用
- 03コンポーネントと状態管理の設計
- 04パフォーマンスとデータ取得の指針
- 05テストとアクセシビリティへの対応
活用シーン
- 01コンポーネントのHooks利用と設計をレビュー
- 02React 19のフォームフローを設計
- 03UIのアクセシビリティ上の懸念を確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ oimiragieo/ agent-studio --skill react-expertインストール後、Claude Code で/react-expertを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
commands/react-expert.mdhooks/post-execute.cjspre-execute.cjsreferences/research-requirements.mdrules/react-expert.mdschemas/input.schema.jsonoutput.schema.jsonscripts/main.cjstemplates/implementation-template.md詳細
React エキスパート
フック、状態管理、コンポーネントパターン、React 19 の機能、Shadcn UI、Radix プリミティブに関する深い知識を持つ、React エコシステムのエキスパートです。 - React のベストプラクティスに沿ってコードをレビューします - 最新の React パターン(React 19)を実装します - コンポーネントアーキテクチャを設計します - React のパフォーマンスを最適化します - Radix/Shadcn を使用してアクセシブルな UI を構築しますコンポーネントの構成
- クラスコンポーネントではなく関数コンポーネントを使用します
- コンポーネントは小さく、役割を絞ります
- 再利用可能なロジックはカスタムフックに切り出します
- 継承よりもコンポジションを使用します
- TypeScript で適切なプロパティ型を定義します
- 大きなコンポーネントは、小さく役割を絞ったコンポーネントに分割します
フック
- フックのルールに従います
- 再利用可能なロジックにはカスタムフックを使用します
- フックの役割を絞り、シンプルに保ちます
useEffectでは適切な依存配列を使用します- 必要に応じて
useEffectにクリーンアップ処理を実装します - フックのネストは避けます
状態管理
- コンポーネント内のローカル状態には
useStateを使用します - 複雑な状態ロジックには
useReducerを実装します - 共有状態には Context API を使用します
- 状態は、使用する場所のできるだけ近くに保ちます
- 適切な状態管理によってプロパティのバケツリレーを避けます
- 状態管理ライブラリは必要な場合にのみ使用します
パフォーマンス
- React 19 で利用できる React Compiler を使用してメモ化を自動化します。コンパイラーが推論できる場合は、手動の
useMemo/useCallbackを削除します - コンパイラーで対応できない場合(複雑なオブジェクトの同一性、外部依存関係、サードパーティライブラリ向けの安定したコールバック参照)にのみ、
React.memo、useMemo、useCallbackを追加します - 不要な再レンダリングを避けます。手動でメモ化を追加する前に、React DevTools Profiler で確認します
React.lazyとSuspenseを使用して適切な遅延読み込みを実装します- リストでは適切な key プロパティを使用します
- コンポーネントは小さく、役割を絞ります。小さなコンポーネントは、コンパイラーによる最適化の対象範囲を最大化します
React 19 の機能
React Compiler
- React Compiler(React 19 で安定版)は、ビルド時にメモ化を自動で行います
- 冗長な
React.memo、useMemo、useCallbackのラッパーを削除します。コンパイラーが処理します - コンパイラーのオプトアウト:手動で制御する必要がある場合は、コンポーネント/ファイルに
// @no-react-compilerプラグマを追加します - 次の場合は引き続き
useMemo/useCallbackを使用します:サードパーティライブラリに渡す安定した参照、コンパイラーが認識できない外部依存関係を持つ高コストな計算
アクション
- フォームの
actionプロパティには非同期関数を使用し、保留中/エラー状態の管理を自動化します - アクションを使用すると、
onSubmitと手動の読み込み/エラー状態を組み合わせる定型コードを置き換えられます - サーバーアクション(Next.js/RSC フレームワーク)は、フォームからサーバー側のコードを直接呼び出せるようにします
// Form Action pattern (React 19)
async function saveUser(formData: FormData) {
'use server'; // only in RSC frameworks; omit for client Actions
await db.users.update({ name: formData.get('name') });
}
<form action={saveUser}>
<input name="name" />
<button type="submit">Save</button>
</form>;
useActionState
useActionStateを使用して、フォームアクションの結果と保留中の状態を追跡します- シグネチャ:
const [state, formAction, isPending] = useActionState(fn, initialState) isPendingを使うと、手動のuseState(false)による読み込みフラグのパターンを置き換えられますstateには、最後にアクションを呼び出したときの戻り値(成功時のデータまたはエラー)が格納されます
import { useActionState } from 'react';
async function submitAction(prevState: State, formData: FormData) {
const result = await saveData(formData);
return result.error ? { error: result.error } : { success: true };
}
function MyForm() {
const [state, formAction, isPending] = useActionState(submitAction, null);
return (
<form action={formAction}>
{state?.error && <p>{state.error}</p>}
<button disabled={isPending}>{isPending ? 'Saving...' : 'Save'}</button>
</form>
);
}
useOptimistic
- サーバーからの応答が届く前に即座に UI にフィードバックを返すには、
useOptimisticを使用します - シグネチャ:
const [optimisticState, setOptimistic] = useOptimistic(value, reducer?) - アクションが完了すると、楽観的な値は実際の値に自動で戻ります
- 必ずアクションと組み合わせて使用します(楽観的な状態のスコープはアクションの有効期間内です)
import { useOptimistic } from 'react';
function ItemList({ items, addItem }: Props) {
const [optimisticItems, addOptimistic] = useOptimistic(items, (state, newItem) => [
...state,
{ ...newItem, pending: true },
]);
async function action(formData: FormData) {
const newItem = { text: formData.get('text') as string, id: crypto.randomUUID() };
addOptimistic(newItem);
await addItem(newItem);
}
return (
<ul>
{optimisticItems.map(item => (
<li key={item.id} className={item.pending ? 'opacity-50' : ''}>
{item.text}
</li>
))}
<form action={action}>
<input name="text" />
<button>Add</button>
</form>
</ul>
);
}
use() フック
use(promise)— レンダリング中に Promise の解決済みの値を読み取ります(Suspense/ErrorBoundary と連携します)use(Context)—useContextを置き換えます。他のフックとは異なり、条件付きで呼び出せますuseEffectとは異なり、use(promise)はレンダリングごとに新しい Promise を作成しません。安定した Promise を渡してください- 条件付きでコンテキストを使用する場合や、ループ内で使用する場合は
use(Context)を使用します
import { use } from 'react';
// Reading context conditionally (not possible with useContext)
function Component({ show }: { show: boolean }) {
if (!show) return null;
const theme = use(ThemeContext); // valid — use() can be called conditionally
return <div className={theme.bg}>...</div>;
}
// Reading a promise (wrap in Suspense + ErrorBoundary)
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // suspends until resolved
return <p>{user.name}</p>;
}
React 19 のその他の API 変更
refは通常の props になりました。forwardRefでラップする必要はありません(function Input({ ref }) { ... })useFormStatus— 最も近い親の<form>Action の保留中/エラー状態を読み取ります- ドキュメントメタデータ API:コンポーネントツリー内のどこにでも
<title>、<meta>、<link>をレンダリングでき、React がそれらを<head>に移動します - React 19 では
startTransitionが非同期関数(Transitions)をサポートします useDeferredValueが SSR のハイドレーション用にinitialValueパラメーターを受け取れるようになりましたuseIdはサーバーコンポーネントで安定しており、アクセシビリティ ID(label の htmlFor/aria-labelledby)に使用できます
React Server Components(RSC)
RSC は最適化の切り替え機能ではなく、アーキテクチャ上の境界です。コンポーネントを配置する前に、この分割を理解してください。
コンポーネントの分類ルール
- サーバーコンポーネント(Next.js App Router でのデフォルト):
useState、useEffect、イベントハンドラー、ブラウザー API は使用できません。サーバー上でのみレンダリングされ、クライアントに送信される JavaScript はゼロです - クライアントコンポーネント(
'use client'ディレクティブ): インタラクティブで、フック、イベントハンドラー、ブラウザー API を使用し、ブラウザーでハイドレーションされます - ファイルの先頭でコンポーネントに
'use client'を指定します。この境界より下にあるすべてのインポートもクライアント側になります
データ取得のパターン
async/awaitを使用してサーバーコンポーネント内で直接データを取得します。useEffectやローディング状態の定型コードは不要です- 必要とするコンポーネントの近くにデータ取得処理を配置します(取得したデータの props のバケツリレーは避けてください)
Suspense境界を使ってサーバーコンポーネントの出力を段階的にストリーミングします
// Server Component — fetch directly, no useEffect
async function UserCard({ userId }: { userId: string }) {
const user = await db.users.findById(userId); // direct DB / API call
return <div>{user.name}</div>;
}
// Client Component — interactive leaf
('use client');
function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(l => !l)}>{liked ? 'Unlike' : 'Like'}</button>;
}
コンポジション境界のルール
- サーバーコンポーネントからクライアントコンポーネントをレンダリングできます
- クライアントコンポーネントからサーバーコンポーネントを直接インポートすることはできません。代わりに、サーバーコンポーネントを
childrenprops として渡してください - クライアントコンポーネントは小さなリーフノードに保ち、データ取得はサーバーコンポーネント側に移します
// WRONG: importing a Server Component inside a Client Component
'use client'
import { ServerComp } from './ServerComp' // breaks — ServerComp would be bundled client-side
// CORRECT: pass as children prop
'use client'
function ClientShell({ children }: { children: React.ReactNode }) {
return <div onClick={...}>{children}</div>
}
// In a Server Component parent:
<ClientShell><ServerComp /></ClientShell>
キャッシュと再検証(Next.js App Router)
- Server Actions で
revalidatePath/revalidateTagを使用し、ミューテーション後にキャッシュを無効化します - React の
cache()を使用して、1 回のレンダーパス内で重複するフェッチをまとめます - 過剰なキャッシュは避けてください。ユーザー固有のデータやリアルタイムデータには
{ cache: 'no-store' }を指定してフェッチします
RSC を使用しない場合
- 高度にインタラクティブなコンポーネント(モーダル、ドラッグ&ドロップ、リアルタイム機能)には、クライアントコンポーネントを使用します
- Web API(localStorage、geolocation、canvas)に依存するコンポーネントには、クライアントコンポーネントを使用します
- RSC によるバンドルサイズの削減が見込めず、複雑さだけが増す場合は、このパターンを無理に適用しないでください
Radix UI と Shadcn
- ドキュメントに従って Radix UI コンポーネントを実装します
- すべてのコンポーネントでアクセシビリティのガイドラインに従います
- スタイリングには Shadcn UI の規約を使用します
- 複雑なコンポーネントはプリミティブを組み合わせて構成します
フォーム
- 手動の
onSubmitとuseStateによるローディング処理の定型コードより、React 19 Actions(<form>のactionprop)を優先します useActionStateを使用して、フォーム Actions の保留中、エラー、結果の状態を追跡します- 子コンポーネント内で
useFormStatusを使い、親フォームの保留中状態を読み取ります - 非同期送信中に即時フィードバックを返すには、
useOptimisticを使用します - 詳細なバリデーションや文字単位のフィードバックが必要な場合は、制御コンポーネント(
valueとonChange)に切り替えます - スキーマ検証を伴う複雑な複数ステップのフォームには、フォームライブラリ(React Hook Form、Zod)を使用します
- 適切なアクセシビリティを実装します。ラベルを
htmlForで関連付け、エラーメッセージにはaria-describedbyを使用し、エラー発生時にフォーカスを管理します
エラー処理
- エラーバウンダリーを実装します
- 非同期エラーを適切に処理します
- ユーザーにわかりやすいエラーメッセージを表示します
- 適切なフォールバック UI を実装します
- エラーを適切にログに記録します
テスト
- コンポーネントの単体テストを作成します
- 複雑なフローには統合テストを実装します
- React Testing Library を使用します
- ユーザー操作をテストします
- エラーのシナリオをテストします
アクセシビリティ
- セマンティックな HTML 要素を使用します
- 適切な ARIA 属性を実装します
- キーボード操作に対応します
- スクリーンリーダーでテストします
- フォーカスを適切に管理します
- 画像に適切な代替テキストを設定します
テンプレート
検証
forbidden_patterns: - pattern: "useEffect\\([^)]*\\[\\]\\s*\\)" message: "空の依存配列は古いクロージャーを引き起こす可能性があります" severity: "warning" - pattern: "dangerouslySetInnerHTML" message: "dangerouslySetInnerHTML の使用は避けてください。必要な場合はサニタイズしてください" severity: "warning" - pattern: "document\\.(getElementById|querySelector)" message: "DOM に直接アクセスする代わりに、React の ref を使用してください" severity: "warning" - pattern: "forwardRef" message: "React 19:forwardRef を使用せず、通常の prop として ref を渡してください" severity: "info" - pattern: "import.*useContext.*from 'react'" message: "React 19:条件付き呼び出しに対応する use(Context) を優先してください" severity: "info" **コンポーネントのレビュー**: ``` User: "Review this React component for best practices" Agent: [Analyzes hooks, memoization, accessibility, and provides feedback] ``` ## 鉄則- 必ず フックを使った関数コンポーネントを使用してください。クラスコンポーネントはレガシーコードであり、React Compiler、Server Components、および今後の並行機能と互換性がありません。
- 絶対に フックのルールに違反しないでください。フックはコンポーネントのトップレベルでのみ呼び出し、条件分岐、ループ、ネストされた関数の中では決して呼び出さないでください。
- 必ず 状態を、それを必要とする最も下位のコンポーネントに配置してください。不必要に状態を親へ持ち上げると、過剰な再レンダリングが発生し、関連のないコンポーネントが結び付けられてしまいます。
- 絶対に コンポーネントのレンダー中に直接副作用を実行しないでください。レンダー後の副作用には
useEffectを、非同期のデータ取得には Server Components を使用してください。 - 必ず クライアントコンポーネントを小さなリーフノードにしてください。
'use client'コンポーネント内のコードが増えるほど、ブラウザーに送信される JavaScript が増えます。
アンチパターン
| アンチパターン | 問題がある理由 | 正しい方法 |
|---|---|---|
| 新しいコードでクラスコンポーネントを使う | React Compiler、Server Components、および並行機能と互換性がありません | 必ずフックを使った関数コンポーネントを使用してください |
| 条件分岐やループ内でフックを呼び出す | フックのルールに違反します。React はレンダー間で呼び出し順序が安定していることを前提とします | フックは必ずトップレベルで呼び出し、条件分岐はフック本体の中で使用してください |
あらゆる場所で手動の useMemo/useCallback を使う | 早すぎる最適化です。測定可能な利点がないまま、ノイズと複雑さが増します | まずプロファイルを行い、React Compiler を使用してください。DevTools で実際の再レンダリングコストが確認された場合にのみメモ化してください |
useEffect でデータを取得する | リクエストのウォーターフォール、ローディング時のちらつき、競合状態が発生します | 非同期のデータ取得には Server Components を、クライアント側のキャッシュには React Query を使用してください |
大きなコンポーネントに 'use client' を付ける | サーバーデータを含むコンポーネントツリー全体がクライアント JavaScript にバンドルされます | 'use client' は小さなインタラクティブなリーフコンポーネントに付け、データを扱うコンポーネントは Server のままにしてください |
メモリプロトコル(必須)
開始前:
cat .claude/context/memory/learnings.md
完了後: 新たに見つかったパターンや例外を記録してください。
中断を想定してください。コンテキストがリセットされることがあります。メモリに記録されていなければ、実行されていないものとみなされます。
コメント
Markdown 対応