📄
概念 📚 beginner-stepup

Reactコンポーネント設計の基礎

Reactのコンポーネント概念・Props・JSXを理解し、UIを再利用可能な部品として構築する考え方を習得する

Reactはユーザーインターフェースを「コンポーネント」という再利用可能な部品に分割して構築するライブラリである。「UIは状態の関数である(UI = f(state))」という考え方が核心で、状態が変わると自動的にUIが更新される。

この章で作るもの(全体像)

基礎編を貫く Todo アプリの第 1 ステップとして、App → TodoList → TodoItem のツリーを props だけで作る。

コンポーネントツリー


コンポーネントの基本

// シンプルなコンポーネント
export function Greeting() {
  return <h1>こんにちは</h1>;
}

// JSX: JavaScript の中に HTML のような構文が書ける
// 実際には React.createElement に変換される

JSXのルール:

  • 必ず一つのルート要素を返す(<>...</> のフラグメントで複数をラップできる)
  • class ではなく className を使う(HTMLと同じ属性名にできないため)
  • {} でJavaScript式を埋め込める

Props(コンポーネントへの入力)

// Props の型定義
interface PatientCardProps {
  name: string;
  age: number;
  status: "active" | "inactive";
}

// Props を受け取るコンポーネント
export function PatientCard({ name, age, status }: PatientCardProps) {
  return (
    <div className="card">
      <h2>{name}</h2>
      <p>年齢: {age}</p>
      <span className={status === "active" ? "text-green-500" : "text-gray-400"}>
        {status}
      </span>
    </div>
  );
}

// 使用側
<PatientCard name="山田太郎" age={45} status="active" />

Propsはコンポーネントへの引数であり、読み取り専用。コンポーネントはPropsを変更しない(純粋関数の考え方)。

リストのレンダリング

const patients = [
  { id: "1", name: "山田太郎", age: 45 },
  { id: "2", name: "佐藤花子", age: 32 },
];

export function PatientList() {
  return (
    <ul>
      {patients.map((patient) => (
        <PatientCard
          key={patient.id}   // keyは必須(Reactが差分更新に使う)
          name={patient.name}
          age={patient.age}
          status="active"
        />
      ))}
    </ul>
  );
}

コンポーネントの分割方針

単一責任原則: 1つのコンポーネントは1つの役割を持つ。大きくなったら分割する目安:

  • 100行を超えたら分割を検討
  • 同じUIパターンが2箇所以上に現れたら共通化
  • 「〇〇カード」「〇〇リスト」「〇〇フォーム」のように役割で命名できるか
Page コンポーネント(ページ全体)
  └─ Layout(ヘッダー・サイドバー・フッター)
       └─ PatientList(患者一覧)
            └─ PatientCard(患者カード)× n
                 └─ StatusBadge(ステータスバッジ)

Propsのバケツリレー問題

コンポーネントツリーが深くなると、上位から下位へPropsを何段も経由して渡す「Propsのドリリング」が発生する。3階層を超えるようなら状態管理ライブラリ(b12参照)の検討サインとなる。

Vite + React Router v7 のファイル構成

このカリキュラムでは web/src/ 以下に手動でファイルを配置し、React Router v7 でルーティングを定義する:

web/src/
├── main.tsx          アプリのエントリーポイント(RouterProvider)
├── router.tsx        ルーティング設定(createBrowserRouter)
├── App.tsx           共通レイアウト(Header + Outlet)
├── pages/
│   ├── Home.tsx      タイムライン(/)
│   ├── Login.tsx     ログイン(/login)
│   └── Profile.tsx   プロフィール(/profile/:username)
└── components/
    ├── PostCard.tsx   投稿カード(再利用可能なUI部品)
    └── Header.tsx    ヘッダーナビゲーション

pages/ がURL対応のページコンポーネント、components/ が再利用される部品コンポーネント。ルーティングの詳細は b14 を参照。

手を動かす

基礎編を通して育てる Todo アプリ の第 1 ステップ。ここでは props だけで静的な一覧を描画する。

やること

  1. web/ ディレクトリで Vite + React + TypeScript のひな型を作る
  2. web/src/components/TodoItem.tsx を作り、textdone を props で受け取る
  3. web/src/App.tsx でハードコードした Todo 配列を TodoItem に map で流し込む
プロジェクト構成(クリックで開く)
web/
├── src/
│   ├── App.tsx
│   ├── main.tsx
│   └── components/
│       └── TodoItem.tsx
└── package.json
セットアップコマンド(クリックで開く)
# 初回のみ:web アプリのひな型を生成
pnpm create vite@latest web -- --template react-ts
cd web
pnpm install
web/src/components/TodoItem.tsx(クリックで開く)
type Props = {
  text: string;
  done: boolean;
};

export function TodoItem({ text, done }: Props) {
  return (
    <li style={{ textDecoration: done ? 'line-through' : 'none' }}>
      {text}
    </li>
  );
}
web/src/App.tsx(クリックで開く)
import { TodoItem } from './components/TodoItem';

const todos = [
  { id: 1, text: 'React のコンポーネント基本を学ぶ', done: true },
  { id: 2, text: 'Props でデータを流す', done: true },
  { id: 3, text: 'リストレンダリングを書く', done: false },
];

export default function App() {
  return (
    <main>
      <h1>Todo</h1>
      <ul>
        {todos.map((t) => (
          <TodoItem key={t.id} text={t.text} done={t.done} />
        ))}
      </ul>
    </main>
  );
}

実行

pnpm dev

成功条件

  • http://localhost:5173 を開くと、3 件の Todo が一覧表示されていること
  • done: true の 2 件に取り消し線が付いていること
  • ブラウザの DevTools Console にエラーが出ていないこと
  • App.tsx の todos 配列に 4 件目を追加すると、画面にも即座に反映されること

次の章(b12)で 入力フォームチェックボックスでの done 切り替え を加える。今は props のみで状態はまだない。


参考リソース


確認クイズ

Q1. JSXで
と書いた場合、どうなるか。

正解: 警告が出るか意図通りに動かない。className を使う必要がある

解説: JSXはJavaScriptの構文拡張であり、class はJavaScriptの予約語。そのため属性名は className に変更されている。for も同様に htmlFor を使う。

Q2. Reactのリストレンダリングで key プロパティが必須な理由はどれか。 A. スタイルを適用するため B. ReactがDOM差分更新を正確に行うため C. Propsを渡すため D. TypeScriptの型チェックのため

正解: B. ReactがDOM差分更新を正確に行うため

解説: key はリストの各要素を一意に識別するためのもの。Reactは再レンダリング時に key を使ってどの要素が追加・変更・削除されたかを判断し、効率的なDOM更新を行う。key がないと誤ったコンポーネントが再利用される可能性がある。

Q3. Propsに ? を付けた場合(例:email?: string)、それはどういう意味か。

正解: そのPropsは省略可能(optional)であり、渡さなくても型エラーにならない

解説: TypeScriptのインターフェース定義でプロパティ名の後に ? を付けると undefined を許容する省略可能フィールドになる。コンポーネント内では emailundefined の可能性があるため、使用時には email && ...email ?? "" などのguardが必要。

Q4. 「UIは状態の関数である(UI = f(state))」という考え方が意味することはどれか。 A. 状態を直接DOMに書き込む B. 状態が変わると自動的にUIが更新される C. UIからしか状態を変更できない D. 状態は1つしか持てない

正解: B. 状態が変わると自動的にUIが更新される

解説: Reactでは開発者が「状態をどう変えるか」だけを記述し、その状態に対応するUIはReactが自動的に計算・更新する。DOMを手動で操作する必要がないのがReactの核心的な設計思想。

Q5. コンポーネントのProps(プロパティ)の特徴として正しいものはどれか。 A. コンポーネント内部から変更できる B. 読み取り専用で、コンポーネントは変更しない C. 親子間でのみ共有できる双方向データ D. 型定義なしで使う必要がある

正解: B. 読み取り専用で、コンポーネントは変更しない

解説: PropsはコンポーネントへのインプットでありImmutable(不変)な値として扱う。純粋関数の考え方と同じく、同じPropsを渡せば同じ出力(UI)を返すことが保証される設計が望ましい。

Q6. web/src/pages/ と web/src/components/ の違いは何か。

正解: pages/ はURLに対応したページコンポーネント、components/ は複数のページやコンポーネントから再利用される部品コンポーネント

解説: pages/Home.tsx/ というURLに対応し、components/PostCard.tsx は複数のページで使い回す。この分類により、ページ固有のロジックと汎用UIパーツを明確に区別できる。

Q7. コンポーネント分割の目安として正しくないものはどれか。 A. 100行を超えたら分割を検討する B. 同じUIパターンが2箇所以上に現れたら共通化する C. ファイル数を減らすために1つの大きなコンポーネントにまとめる D. 「〇〇カード」「〇〇リスト」などと役割で命名できるか確認する

正解: C. ファイル数を減らすために1つの大きなコンポーネントにまとめる

解説: 単一責任原則に基づき、1つのコンポーネントは1つの役割を持つべき。ファイル数を減らすためにまとめると再利用性・テスト容易性・可読性が損なわれる。

Q8. JSXで複数の要素を返したいとき、余分なHTMLタグを追加せずにまとめる方法はどれか。 A.
...
で必ず包む B. <>... フラグメントを使う C. 配列として返す(戻り値を [] に変える) D. コンポーネントを分割する

正解: B. <>...</> フラグメントを使う

解説: React.Fragment(省略記法 <>...</>)を使うと、実際のDOM要素を追加せずに複数の要素を1つのルート要素としてまとめられる。不要な <div> の追加を避けてHTMLをクリーンに保てる。

Q9. Propsのドリリングが問題になり始める目安は何階層を超えたときか。

正解: 3階層

解説: Props を3階層以上にわたって渡し続けると、中間コンポーネントが実際には使わないデータを「通過させるだけ」になりコードの見通しが悪化する。このタイミングで Context APIや状態管理ライブラリの導入を検討する(b12参照)。

Q10. React Router v7 で共通レイアウト(ヘッダーなど)を全ページに適用するために使うコンポーネント・概念はどれか。 A. index.tsx の直接描画 B. App.tsx に を使ったレイアウトパターン C. 各ページコンポーネントにHeaderを個別にインポートする D. router.tsx にスタイルを直接書く

正解: B. App.tsx<Outlet /> を使ったレイアウトパターン

解説: React Router v7 では App.tsx を共通レイアウトコンポーネントとして定義し、<Outlet /> が子ルートのコンポーネントを描画するプレースホルダーになる。これにより Header などを一箇所に記述するだけで全ページに適用できる。

生きているコード

本ドキュメントで扱ったパターンの完全な動作コードは、メンター側リポジトリの参照ブランチで確認できます。

ブランチの作り方・見方は b00-curriculum-map を参照してください。

📚 beginner-stepup 全 53 章
導入編 13 章
  1. 1. 📄Web とは何か
  2. 2. 📄URL を打ってから画面が表示されるまで
  3. 3. 📄ネットワーク基礎(TCP/IP・DNS・HTTPS)
  4. 4. 📄【発展】物理層から通信が成立するまで(電力・Ethernet・Wi-Fi・Bluetooth)
  5. 5. 📄WSL2・Docker セットアップ詳細(Windows 向け)
  6. 6. 📄環境構築の段階的導入(macOS / Windows)
  7. 7. 📄カリキュラム全体マップ(Week × 教材 × 参照ブランチ × 要求チェックリスト)
  8. 8. 📄このカリキュラムの使い方(SQL・Python・Dify経験者向け)
  9. 9. 📄シェル・ターミナル基礎
  10. 10. 📄Windows で完全にゼロから始める開発環境構築(Week 1)
  11. 11. 📄Git基礎
  12. 12. 📄GitHubワークフロー
  13. 13. 📄パッケージ管理(pnpm workspace)
応用編 16 章
  1. 1. 📄AWSインフラ基礎
  2. 2. 📄AWS Budget Alert の設定(Month 5 Week 17)
  3. 3. 📄環境変数管理
  4. 4. 📄Bastion EC2 と SSH ProxyJump(Month 5 Week 18)
  5. 5. 📄CI/CD基礎
  6. 6. 📄ECR への Docker イメージ push と App EC2 デプロイ(Month 5 Week 19)
  7. 7. 📄テスト設計の基本
  8. 8. 📄CloudFront + S3 + ALB で公開する(Month 5 Week 20)
  9. 9. 📄CLAUDE.md・プロジェクト設定
  10. 10. 📄PR レビュー 5 観点ルーブリック(全 Week 共通)
  11. 11. 📄タスク分解・仕様の書き方
  12. 12. 📄Playwright で E2E テスト(Month 6 Week 22)
  13. 13. 📄生成コードのレビュー・デバッグの勘所
  14. 14. 📄Trivy で脆弱性スキャン(Month 6 Week 23)
  15. 15. 📄CloudWatch Logs の読み方と運用(Month 6 Week 23)
  16. 16. 📄PDF ポートフォリオの自動生成(Month 6 Week 24)