mcpField
R
react-dev

react-dev

github.com/softaworks開発2.6k4.1k 回インストール34 週間前に更新
リポジトリ

react-dev について

TypeScript を使った型安全な React 開発のためのリファレンススキルです。コンポーネント、React 19 のパターン、型付きルーティングを扱い、一般的な TypeScript や素の JavaScript React は対象外です。

主な機能

  1. 01コンポーネントの props と children の型付け
  2. 02React 19 API と移行パターンの解説
  3. 03型付きフックとイベントハンドラーの例
  4. 04ジェネリックコンポーネントの設計
  5. 05Server Components と Server Actions の解説
  6. 06TanStack Router と React Router の型付け

活用シーン

  1. 01再利用可能な React コンポーネントの型付け
  2. 02フォームとイベントコールバックの安全な処理
  3. 03React 19 パターンへのコード移行
  4. 04型付きルートを使うアプリの構築

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/softaworks/agent-toolkit --skill react-dev

インストール後、Claude Code で/react-devを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
examples/
generic-components.md
server-components.md
references/
event-handlers.md
hooks.md
react-19-patterns.md
react-router.md
tanstack-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 する
- [hooks.md](references/hooks.md) - useState、useRef、useReducer、useContext、カスタムフック - [event-handlers.md](references/event-handlers.md) - すべてのイベントタイプ、ジェネリックハンドラー - [react-19-patterns.md](references/react-19-patterns.md) - useActionState、use()、useOptimistic、移行 - [generic-components.md](examples/generic-components.md) - Table、Select、List、Modal のパターン - [server-components.md](examples/server-components.md) - 非同期コンポーネント、Server Actions、ストリーミング - [tanstack-router.md](references/tanstack-router.md) - TanStack Router の型付きルート、検索パラメータ、ナビゲーション - [react-router.md](references/react-router.md) - React Router v7 のローダー、アクション、型生成、フォーム

コメント