C
React//Lesson 22

TypeScript + React — コンポーネントの型定義、Genericコンポーネント

35分·theory
javascript

TypeScript + React — コンポーネントの型定義、Genericコンポーネント

💡 なぜ学ぶべきか?

🎯 コードを書きながらバグを発見し、デプロイ後の障害を減らします。
💼 IDEの自動補完により開発速度が30%向上します。
コード変更時に安全にリファクタリングできます。
🔗 2025年現在、中規模以上の企業はすべてTypeScriptを必須としています。
🏢 실무에서는
実務では「この関数がどの型を受け取るか」をPropsの型として明確に定義することで、チームメンバーがミスなくコンポーネントを使用できます。Genericコンポーネントを作成すれば、ボタン・入力フィールド・ドロップダウンなど多様なUIを単一のコードで管理でき、メンテナンスがはるかに楽になります。

概念

TypeScriptとReactを組み合わせてコンポーネントのPropsの型を定義し、再利用可能なGenericコンポーネントを実装することは、2025年現在のフロントエンド開発における必須スキルです。ランタイムエラーをコンパイル時に検出し、IDEの自動補完とリファクタリングを安全に活用することで、大規模プロジェクトにおける生産性と保守性を大幅に向上できます。

なぜ重要なのか?

実務では、コンポーネントのPropsに誤った型のデータを渡したり、APIのレスポンス構造が変わった際に関連コンポーネントの修正漏れによってランタイムエラーが発生するケースが頻繁にあります。また、Table、Modal、Formといった共通コンポーネントを複数のドメインで再利用する場合、Genericを使わないと各ドメインで重複コードを書くことになり、保守コストが指数関数的に増大します。

核心概念

TypeScript + Reactのコンポーネント型定義は、「LEGOブロックに取扱説明書を添付すること」に例えられます。各コンポーネントがどのPropsを受け取り、どのような形のデータを扱うかを明示することで、他の開発者(または将来の自分)が安全に組み立てられるようになります。Genericコンポーネントは「汎用テンプレート」として機能し、1つのコンポーネントでさまざまなデータ型を扱える高度なパターンです。

重要ポイント

  • Propsインターフェースによるコンポーネント契約の明示
  • Genericを活用した型安全な再利用コンポーネント
  • React 19の新しい型機能と組み合わせたパターン
💻 悪い例 — 型のないコンポーネントと`any`の乱用
// ❌ 型安全性のないコンポーネント
function UserCard({ user }: any) {
  return (
    <div className="card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <span>{user.age}歳</span>
    </div>
  );
}

// ❌ 使用時も型チェックなし
function App() {
  const userData: any = {
    userName: "キム開発", // nameではなくuserName
    mail: "[email protected]", // emailではなくmail
    years: 30 // ageではなくyears
  };
  
  return <UserCard user={userData} />; // ランタイムでエラーを発見
}
💻 良い例 — 2025年推奨の型安全なコンポーネント
// ✅ 明確なPropsの型定義
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  avatar?: string; // optional property
}

interface UserCardProps {
  user: User;
  onClick?: (userId: number) => void;
  showAge?: boolean;
}

// ✅ React 19の最新関数コンポーネントパターン
function UserCard({ user, onClick, showAge = true }: UserCardProps) {
  return (
    <div 
      className="card" 
      onClick={() => onClick?.(user.id)}
      role="button"
      tabIndex={0}
    >
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      {showAge && <span>{user.age}歳</span>}
      {user.avatar && <img src={user.avatar} alt={`${user.name} プロフィール`} />}
    </div>
  );
}

// ✅ 型安全な使用
function App() {
  const userData: User = {
    id: 1,
    name: "キム開発",
    email: "[email protected]",
    age: 30
  };
  
  const handleUserClick = (userId: number) => {
    console.log(`User ${userId} clicked`);
  };
  
  return (
    <UserCard 
      user={userData}
      onClick={handleUserClick}
      showAge={false}
    />
  );
}
💻 応用例 — Generic再利用可能テーブルコンポーネント
// ✅ Genericを活用した汎用テーブルコンポーネント
interface Column<T> {
  key: keyof T;
  title: string;
  width?: string;
  render?: (value: T[keyof T], record: T, index: number) => React.ReactNode;
}

interface DataTableProps<T> {
  data: T[];
  columns: Column<T>[];
  loading?: boolean;
  onRowClick?: (record: T, index: number) => void;
  emptyText?: string;
}

// ✅ React 19 + TypeScript 5.x 最新パターン
function DataTable<T extends Record<string, any>>({
  data,
  columns,
  loading = false,
  onRowClick,
  emptyText = "データがありません"
}: DataTableProps<T>) {
  if (loading) {
    return <div className="loading">読み込み中...</div>;
  }

  if (data.length === 0) {
    return <div className="empty">{emptyText}</div>;
  }

  return (
    <table className="data-table">
      <thead>
        <tr>
          {columns.map((column) => (
            <th key={String(column.key)} style={{ width: column.width }}>
              {column.title}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {data.map((record, index) => (
          <tr 
            key={index}
            onClick={() => onRowClick?.(record, index)}
            className={onRowClick ? 'clickable' : ''}
          >
            {columns.map((column) => (
              <td key={String(column.key)}>
                {column.render 
                  ? column.render(record[column.key], record, index)
                  : String(record[column.key] ?? '')
                }
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

// ✅ 使用例 - ユーザーテーブル
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
  lastLogin: Date;
}

function UserManagement() {
  const users: User[] = [
    { id: 1, name: "キム開発", email: "[email protected]", role: "admin", lastLogin: new Date() }
  ];

  const userColumns: Column<User>[] = [
    { key: 'name', title: '名前', width: '150px' },
    { key: 'email', title: 'メールアドレス', width: '200px' },
    { 
      key: 'role', 
      title: '権限', 
      width: '100px',
      render: (role: User['role']) => (
        <span className={`badge ${role}`}>
          {role === 'admin' ? '管理者' : 'ユーザー'}
        </span>
      )
    },
    {
      key: 'lastLogin',
      title: '最終ログイン',
      render: (date: Date) => date.toLocaleDateString('ko-KR')
    }
  ];

  return (
    <DataTable<User>
      data={users}
      columns={userColumns}
      onRowClick={(user) => console.log('選択されたユーザー:', user.name)}
      emptyText="登録されたユーザーがいません"
    />
  );
}

💡 ⚠️ よくあるミス

  • Propsインターフェースでオプションと必須を区別せず全フィールドを必須にしてしまい、コンポーネントの再利用性を損なうケース
  • Genericコンポーネントでextendsの制約条件を適切に設定せず、ランタイムでundefinedエラーが発生するケース
  • イベントハンドラやコールバック関数のパラメーター型をanyで定義してしまい、呼び出し側で型安全性が失われるケース

💡 🎯 面接対策

Q: ReactコンポーネントでのProps型定義の方法と、interfacetypeの違いを説明してください。
Q: Genericコンポーネントはどのような場面で有用で、実際のプロジェクトではどのように活用しましたか?

ヒント: Propsの型定義 → interfaceを使う理由(拡張性)→ オプション vs. 必須の使い分け → Genericの活用例(Table、Modal、Form)→ 型安全性とコード再利用性の向上効果 → 実際のプロジェクトへの適用経験

⚛️ Reactパターン — TypeScript + React — コンポーネントの型定義、Genericコンポーネント

TypeScript + React — コンポーネントの型定義、Genericコンポーネントの使い方を、コードを交えながらステップごとに学んでいきましょう。
1 🧩 1. コンポーネントの定義
関数がそのままコンポーネント
function Greeting({ name }) {
  return <h1>こんにちは、{name}!</h1>;
}
2 📤 2. Propsの受け渡し
親から子への単方向データフロー
<Greeting name="山田太郎" />
3 🔁 3. 再利用
同じコンポーネントを、異なるpropsで複数回使用
<Greeting name="Alice" />
<Greeting name="Bob" />
4 💡 4. 単方向データフロー
子が親のstateを変更するには、コールバック関数をpropsとして受け取る

🎮 TypeScript + React — コンポーネントの型定義、Genericコンポーネント — ステップごとの理解

各ステップをクリックして内容を確認し、「理解できました」ボタンで進捗を確認してください。
🖥️ 実行結果 — レンダリングされたReactコンポーネント
✏️ React 코드 수정하기 (클릭해서 열기)
⚛️ React 18 + Babel Standalone — まず結果を確認し、エディタで自由に編集できます。

確認クイズ

Reactコンポーネントのprops型を定義する際に推奨される方法は?
💡 interface Props { name: string; age: number } またはtype Props = {...}と定義し、function Component({ name, age }: Props)のように使います。TypeScriptがpropsの型エラーをコンパイル時に検出します。
TypeScript + React - React