react-dev について
TypeScript を使った型安全な React 開発のためのリファレンススキルです。コンポーネント、React 19 のパターン、型付きルーティングを扱い、一般的な TypeScript や素の JavaScript React は対象外です。
主な機能
- 01コンポーネントの props と children の型付け
- 02React 19 API と移行パターンの解説
- 03型付きフックとイベントハンドラーの例
- 04ジェネリックコンポーネントの設計
- 05Server Components と Server Actions の解説
- 06TanStack Router と React Router の型付け
活用シーン
- 01再利用可能な React コンポーネントの型付け
- 02フォームとイベントコールバックの安全な処理
- 03React 19 パターンへのコード移行
- 04型付きルートを使うアプリの構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ softaworks/ agent-toolkit --skill react-devインストール後、Claude Code で/react-devを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdexamples/generic-components.mdserver-components.mdreferences/event-handlers.mdhooks.mdreact-19-patterns.mdreact-router.mdtanstack-router.md詳細
React TypeScript
型安全な React = コンパイル時の保証 = 自信を持ったリファクタリング。
<when_to_use>
- 型付けされた React コンポーネントの構築
- ジェネリックコンポーネントの実装
- イベントハンドラー、フォーム、refの型付け
- React 19 の機能(Actions、Server Components、use())の利用
- ルーター統合(TanStack Router、React Router)
- 適切な型付けを伴うカスタムフックの作成
対象外: React を使わない TypeScript、素の JS React
</when_to_use>
<react_19_changes>
React 19 には移行が必要な破壊的変更があります。主要なパターンは以下のとおりです。
ref as prop - forwardRef は非推奨です:
// React 19 - ref as regular prop
type ButtonProps = {
ref?: React.Ref<HTMLButtonElement>;
} & React.ComponentPropsWithoutRef<'button'>;
function Button({ ref, children, ...props }: ButtonProps) {
return <button ref={ref} {...props}>{children}</button>;
}
useActionState - useFormState の代替です:
import { useActionState } from 'react';
type FormState = { errors?: string[]; success?: boolean };
function Form() {
const [state, formAction, isPending] = useActionState(submitAction, {});
return <form action={formAction}>...</form>;
}
use() - promise/context をアンラップします:
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // Suspends until resolved
return <div>{user.name}</div>;
}
useOptimistic、useTransition、移行チェックリストについては react-19-patterns.md を参照してください。
</react_19_changes>
<component_patterns>
Props - ネイティブ要素を拡張します:
type ButtonProps = {
variant: 'primary' | 'secondary';
} & React.ComponentPropsWithoutRef<'button'>;
function Button({ variant, children, ...props }: ButtonProps) {
return <button className={variant} {...props}>{children}</button>;
}
Children の型付け:
type Props = {
children: React.ReactNode; // Anything renderable
icon: React.ReactElement; // Single element
render: (data: T) => React.ReactNode; // Render prop
};
バリantプロパティ向けの判別可能なユニオン:
type ButtonProps =
| { variant: 'link'; href: string }
| { variant: 'button'; onClick: () => void };
function Button(props: ButtonProps) {
if (props.variant === 'link') {
return <a href={props.href}>Link</a>;
}
return <button onClick={props.onClick}>Button</button>;
}
</component_patterns>
<event_handlers>
正確なターゲットの型付けのために、具体的なイベント型を使用します:
// Mouse
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.currentTarget.disabled = true;
}
// Form
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
}
// Input
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
// Keyboard
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') e.currentTarget.blur();
}
focus、drag、clipboard、touch、wheel イベントについては event-handlers.md を参照してください。
</event_handlers>
<hooks_typing>
useState - ユニオンや null の場合は明示的に指定します:
const [user, setUser] = useState<User | null>(null);
const [status, setStatus] = useState<'idle' | 'loading'>('idle');
useRef - DOM には null、可変値には値を指定します:
const inputRef = useRef<HTMLInputElement>(null); // DOM - use ?.
const countRef = useRef<number>(0); // Mutable - direct access
useReducer - アクションには判別可能なユニオンを使用します:
type Action =
| { type: 'increment' }
| { type: 'set'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'set': return { ...state, count: action.payload };
default: return state;
}
}
カスタムフック - as const を付けてタプルを返します:
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue(v => !v);
return [value, toggle] as const;
}
useContext - null ガードパターン:
const UserContext = createContext<User | null>(null);
function useUser() {
const user = useContext(UserContext);
if (!user) throw new Error('useUser outside UserProvider');
return user;
}
useCallback、useMemo、useImperativeHandle、useSyncExternalStore については hooks.md を参照してください。
</hooks_typing>
<generic_components>
ジェネリックコンポーネントは props から型を推論します。呼び出し側での手動の型注釈は不要です。
パターン - 列キーには keyof T、カスタム描画にはレンダープロップを使用します:
type Column<T> = {
key: keyof T;
header: string;
render?: (value: T[keyof T], item: T) => React.ReactNode;
};
type TableProps<T> = {
data: T[];
columns: Column<T>[];
keyExtractor: (item: T) => string | number;
};
function Table<T>({ data, columns, keyExtractor }: TableProps<T>) {
return (
<table>
<thead>
<tr>{columns.map(col => <th key={String(col.key)}>{col.header}</th>)}</tr>
</thead>
<tbody>
{data.map(item => (
<tr key={keyExtractor(item)}>
{columns.map(col => (
<td key={String(col.key)}>
{col.render ? col.render(item[col.key], item) : String(item[col.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
必須プロパティのための制約付きジェネリクス:
type HasId = { id: string | number };
function List<T extends HasId>({ items }: { items: T[] }) {
return <ul>{items.map(item => <li key={item.id}>...</li>)}</ul>;
}
Select、List、Modal、FormField のパターンについては generic-components.md を参照してください。
</generic_components>
<server_components>
React 19 の Server Components はサーバー上で実行され、async にできます。
非同期データ取得:
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await fetchUser(params.id);
return <div>{user.name}</div>;
}
Server Actions - 変更処理には 'use server' を使用します:
'use server';
export async function updateUser(userId: string, formData: FormData) {
await db.user.update({ where: { id: userId }, data: { ... } });
revalidatePath(`/users/${userId}`);
}
Client + Server Action:
'use client';
import { useActionState } from 'react';
import { updateUser } from '@/actions/user';
function UserForm({ userId }: { userId: string }) {
const [state, formAction, isPending] = useActionState(
(prev, formData) => updateUser(userId, formData), {}
);
return <form action={formAction}>...</form>;
}
promise の受け渡しのための use():
// Server: pass promise without await
async function Page() {
const userPromise = fetchUser('123');
return <UserProfile userPromise={userPromise} />;
}
// Client: unwrap with use()
'use client';
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
並列フェッチ、ストリーミング、エラーバウンダリについては server-components.md を参照してください。
</server_components>
TanStack Router と React Router v7 はいずれも型安全なルーティングソリューションを提供します。
TanStack Router - Zod バリデーションによるコンパイル時の型安全性:
import { createRoute } from '@tanstack/react-router';
import { z } from 'zod';
const userRoute = createRoute({
path: '/users/$userId',
component: UserPage,
loader: async ({ params }) => ({ user: await fetchUser(params.userId) }),
validateSearch: z.object({
tab: z.enum(['profile', 'settings']).optional(),
page: z.number().int().positive().default(1),
}),
});
function UserPage() {
const { user } = useLoaderData({ from: userRoute.id });
const { tab, page } = useSearch({ from: userRoute.id });
const { userId } = useParams({ from: userRoute.id });
}
React Router v7 - Framework Mode による自動型生成:
import type { Route } from "./+types/user";
export async function loader({ params }: Route.LoaderArgs) {
return { user: await fetchUser(params.userId) };
}
export default function UserPage({ loaderData }: Route.ComponentProps) {
const { user } = loaderData; // Typed from loader
return <h1>{user.name}</h1>;
}
TanStack のパターンについては tanstack-router.md、React Router のパターンについては react-router.md を参照してください。
必ず行うこと:
- 具体的なイベント型を使用する(MouseEvent、ChangeEvent など)
- ユニオンや null には明示的な useState を使用する
- ネイティブ要素の拡張には ComponentPropsWithoutRef を使用する
- バリアントプロパティには判別可能なユニオンを使用する
- タプルを返す場合は as const を使用する
- React 19 では ref を prop として使用する(forwardRef は使わない)
- フォームアクションには useActionState を使用する
- 型安全なルーティングパターンを使用する(ルーティングのセクションを参照)
決して行わないこと:
- イベントハンドラーに any を使用する
- children に JSX.Element を使用する(ReactNode を使用する)
- React 19 以降で forwardRef を使用する
- useFormState を使用する(非推奨)
- DOM ref の null 処理を忘れる
- 同じファイルで Server コンポーネントと Client コンポーネントを混在させる
- use() に渡す前に promise を await する