Reactコンポーネントの基本:関数型とクラス型コンポーネントの徹底解説

2025-08-07

はじめに

この記事では、Reactアプリケーションを構成する最も重要な概念「コンポーネント」について、初学者向けにていねいに解説します。

コンポーネントとは、UIを部品に分けて管理するための仕組みです。難しく聞こえるかもしれませんが、「ページの見た目をパーツ単位で作る」というシンプルなアイデアです。この記事を読み終えると、関数型・クラス型の両方のコンポーネントを理解し、実際に書けるようになります。

コンポーネントとは何か?

コンポーネントの基本概念

コンポーネントは「Reactアプリを構成する独立したUI部品」です。たとえばWebサイトのヘッダー・ナビゲーション・カード・ボタンなど、画面を構成するパーツひとつひとつがコンポーネントになります。

コンポーネントには以下の3つの特徴があります。

  • 自己完結的:特定の見た目と機能をひとつのまとまりに収める
  • 再利用可能:同じコンポーネントを複数の場所で使い回せる
  • 組み合わせ可能:小さな部品を組み合わせて、複雑な画面を構築できる

まずは最もシンプルなコンポーネントを見てみましょう。

// シンプルなボタンコンポーネントの例
function Button() {
  return <button>クリックしてください</button>;
}

これだけで「ボタン」というUI部品のコンポーネントが完成します。

コンポーネントを使う3つのメリット

コンポーネントを使って開発するメリットは主に3点あります。

  • コードの再利用:ボタンやカードなどを1回定義すれば、何度でも使い回せる
  • メンテナンスのしやすさ:修正したいときに該当のコンポーネントだけ変更すればよい
  • チーム開発の効率化:別々の開発者が別々のコンポーネントを並行して作れる

関数型コンポーネント

基本の書き方

現在のReact開発では、関数型コンポーネントが標準的な書き方です。JavaScriptの関数としてコンポーネントを定義し、JSXを返すだけで動作します。

function Greeting() {
  return <h1>こんにちは、Reactの世界へ!</h1>;
}

アロー関数での書き方

ES6のアロー関数を使うと、より短く書けます。どちらの書き方でも動作は同じです。

// 通常のアロー関数
const Greeting = () => {
  return <h1>こんにちは、Reactの世界へ!</h1>;
};

// 1行で返せる場合はさらに短く書ける(暗黙のreturn)
const Greeting = () => <h1>こんにちは、Reactの世界へ!</h1>;

最初のうちはどちらでも構いませんが、複数行になる場合は前者の書き方の方が読みやすいです。

実用的な関数型コンポーネントの例(アロー関数使用)

コンポーネントの中で変数を定義したり、map() でリストを展開したりすることもできます。

// function UserProfile() {
  // アロー関数を使わない場合
const UserProfile = () => {
  // コンポーネント内で変数を定義できる
  const user = {
    name: '山田太郎',
    age: 28,
    hobbies: ['読書', '旅行', '写真']
  };

  return (
    <div className="profile">
      <h2>{user.name}</h2>
      <p>年齢: {user.age}歳</p>
      <h3>趣味:</h3>
      <ul>
        {user.hobbies.map((hobby, index) => (
          <li key={index}>{hobby}</li>
        ))}
      </ul>
    </div>
  );
};

関数型コンポーネントの特徴まとめ

  • シンプルな構文:クラスより短く直感的に書ける
  • Hooks対応:React 16.8以降、useState などのHooksで状態管理もできる
  • 現在の主流:新規プロジェクトでは関数型コンポーネントが推奨される

クラス型コンポーネント(昔のやり方)

クラス型コンポーネントは、this の扱いが難しくコードが複雑になりやすいことや、状態管理やライフサイクル処理を書くための記述量が多いことから、開発効率や可読性に課題がありました。その後、ReactでHooksが導入され、関数型コンポーネントでもstateやライフサイクル機能を簡潔に扱えるようになったため、現在はシンプルで保守しやすい関数型コンポーネントが主流になっています。

基本の書き方

クラス型コンポーネントは、ES6の class 構文を使って定義します。React.Component を継承し、render() メソッドの中にJSXを書きます。

import React from 'react';

class Greeting extends React.Component {
  render() {
    return <h1>こんにちは、Reactの世界へ!</h1>;
  }
}

関数型と比べると記述量が増えますが、動作は同じです。ただし現在は関数型に移行してます。

クラス型コンポーネントの構造

クラス型コンポーネントには、いくつかの決まった要素があります。

  • React.Component の継承(必須)
  • render() メソッド(必須):JSXを返す
  • constructor()(任意):stateの初期化など

以下は状態(state)を持つカウンターの例です。

class Counter extends React.Component {
  constructor(props) {
    super(props);             // 必ず最初に呼ぶ
    this.state = { count: 0 }; // stateの初期値を設定
  }

  render() {
    return (
      <div>
        <p>現在のカウント: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          増やす
        </button>
      </div>
    );
  }
}

クラス型では状態の取得に this.state、更新に this.setState() を使います。

クラス型コンポーネントの特徴まとめ

  • ライフサイクルメソッド:コンポーネントの表示・更新・破棄のタイミングで処理を実行できる
  • this の使用:メソッドやプロパティへのアクセスに this が必要
  • 歴史的経緯:Hooks登場前は状態管理にクラスが必須だった

関数型とクラス型の比較

2つのコンポーネントの書き方を表でまとめます。

特徴関数型コンポーネントクラス型コンポーネント
定義方法関数classextends React.Component
状態管理useState Hookthis.state
ライフサイクルuseEffect Hookライフサイクルメソッド
this の使用不要必要
コード量少ない多い
現在の推奨✅ 推奨既存コードの読み書きに必要

現在のReact(バージョン16.8以降)では、関数型コンポーネント+Hooksの使用が推奨されています。ただし、既存のプロジェクトやライブラリでクラス型が使われている場面もあるため、読み方は理解しておきましょう。

コンポーネントの命名規則

コンポーネント名は必ずパスカルケース(大文字始まり)で書きます。これはReactの仕様で、大文字で始まる要素をカスタムコンポーネント、小文字で始まる要素をHTMLタグとして区別しています。

// ✅ 正しい例(パスカルケース)
function UserProfile() { /* ... */ }
class ShoppingCart extends React.Component { /* ... */ }

// ❌ 誤った例(小文字始まり)
function userProfile() { /* ... */ }      // Reactがコンポーネントと認識しない
class shopping_cart extends React.Component { /* ... */ }

名前はコンポーネントの役割がひと目でわかるように付けましょう。Component1MyComp のような名前は避けてください。

コンポーネントの分割

いつ分割すべきか?

以下のような場合に、コンポーネントの分割を検討します。

  • 1つのコンポーネントが長くなってきたとき
  • 同じUIパターンが何度も繰り返されるとき
  • 複数の異なる役割が1箇所にまとまっているとき

分割の例

ユーザーカードを例に、分割前と分割後を比べてみましょう。

// 分割前:1つのコンポーネントにすべての要素が入っている
const UserProfile = () => {
  // ...アバター・情報・趣味のコードが全部ここに...
};

// 分割後:役割ごとに小さなコンポーネントに分ける
const UserAvatar   = () => { /* アバター画像 */ };
const UserInfo     = () => { /* 名前・年齢など */ };
const UserHobbies  = () => { /* 趣味リスト */ };

// 親コンポーネントで組み合わせる
const UserProfile = () => (
  <div className="user-profile">
    <UserAvatar />
    <UserInfo />
    <UserHobbies />
  </div>
);

分割することで、それぞれのコンポーネントが「何をするか」明確になります。

コンポーネントの実践例

関数型コンポーネント:Todoアイテム

Todoアイテムを表示する関数型コンポーネントです。task(タスク名)と completed(完了状態)を受け取り、完了済みの場合はクラスを切り替えます。taskcompleted のような「外から受け取る値」を props(プロップス) と呼びます(詳しくは次の記事で解説します)。

const TodoItem = ({ task, completed }) => {
  return (
    <li className={`todo-item ${completed ? 'completed' : ''}`}>
      <input type="checkbox" checked={completed} readOnly />
      <span>{task}</span>
    </li>
  );
};

// 使用例
<TodoItem task="Reactを学ぶ" completed={false} />

クラス型コンポーネント:タイマー

1秒ごとにカウントアップするタイマーです。componentDidMount でタイマーを開始し、componentWillUnmount で停止します。このような「コンポーネントの表示・削除のタイミングで処理を行う仕組み」をライフサイクルと呼びます(詳しくは後の記事で解説します)。

class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { seconds: 0 };
  }

  // コンポーネントが画面に表示されたタイミングで実行
  componentDidMount() {
    this.interval = setInterval(() => {
      this.setState(prevState => ({
        seconds: prevState.seconds + 1
      }));
    }, 1000);
  }

  // コンポーネントが画面から消えるタイミングで実行(タイマーを止める)
  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    return (
      <div>
        <p>経過時間: {this.state.seconds}秒</p>
      </div>
    );
  }
}

コンポーネントのファイル構成

プロジェクトが大きくなってきたら、コンポーネントをファイルごとに分けて管理します。以下のような構成が一般的です。

src/
  components/
    Button/
      Button.jsx    ← コンポーネント本体
      Button.css    ← スタイル
      index.js      ← エクスポート用
    Header/
      Header.jsx
      Header.css
      index.js

index.js にエクスポートをまとめておくと、インポート時にフォルダ名だけで参照できて便利です。

// src/components/Button/index.js
export { default } from './Button';

// 使用側(フォルダ名だけでインポートできる)
import Button from './components/Button';

よくあるエラーと解決策

コンポーネントを書く際によく遭遇するエラーと対処法をまとめます。

エラー原因解決策
コンポーネントが表示されないコンポーネント名が小文字始まり/エクスポートが抜けている名前を大文字始まりにする/export を確認する
TypeError: Cannot read property…this のバインド漏れ(クラス型)/propsが未定義this のバインドを確認/propsの存在チェックを追加
Unexpected tokenJSXのタグ閉じ忘れや構文ミスエラー行のJSXを確認し、タグを正しく閉じる

デバッグの基本:コンソールログ

コンポーネントに渡されているデータを確認したいときは、console.log() を使います。

const MyComponent = (props) => {
  console.log('Props:', props); // ブラウザの開発者ツールで確認できる
  return <div>...</div>;
};

コンポーネント設計のベストプラクティス

コンポーネントを設計するうえで意識したいポイントをまとめます。

  • 単一責任の原則:1つのコンポーネントに1つの役割だけを持たせる
  • 適切な分割:行数が多くなってきたら分割を検討する
  • 命名の明確化:コンポーネント名から役割がわかるように付ける
  • プレゼンテーション/コンテナの分離:見た目だけを担当するコンポーネントと、データ処理を担当するコンポーネントを分ける

まとめ

今回の内容は、関数型コンポーネントの基本やパスカルケースによる命名規則、コンポーネント同士を組み合わせて構成する考え方を確認しました。また、コンポーネント内でのデータ定義やリスト表示の方法、関数型とクラス型の書き方の違い、stateの基本的な扱い方についても練習しました。さらに、コンポーネントの適切な分割やエラー修正の進め方に加え、プレゼンテーションとコンテナの役割分担、stateの応用的な使い方、そして全体のコンポーネントツリー設計について理解を深める内容となっていました。


🟢 初級問題(問題1〜3)

関数型コンポーネントの基本的な書き方と、パスカルケースの命名規則を練習します。

問題1:最初のコンポーネントを作ろう

【問題】
以下の条件をすべて満たす関数型コンポーネントを作成してください。

  • コンポーネント名は WelcomeCard とする
  • 以下の情報を表示する
    • <h2> タグで「Reactへようこそ!」
    • <p> タグで「コンポーネントの学習を始めましょう。」
  • 関数宣言(function)とアロー関数(const)の両方で書いてみること

【期待する表示】

Reactへようこそ!
コンポーネントの学習を始めましょう。

問題2:コンポーネントの命名ミスを修正しよう

【問題】
以下のコードには命名規則・構文に関するミスが4箇所あります。すべて修正してください。

function userCard() {
  return (
    <div>
      <h2>田中太郎</h2>
      <p>エンジニア</p>
    </div>
  );
}

const app = () => {
  return (
    <div>
      <userCard />
      <userCard />
    </div>
  );
};

【問題箇所のヒント】

  • コンポーネント名がパスカルケースになっていない箇所が複数ある
  • コンポーネントを使用している箇所でも名前が小文字になっている

【期待する動作】

 App コンポーネントを表示すると、UserCard が2つ並んで表示される。

問題3:コンポーネントを組み合わせよう

【問題】
以下の3つのコンポーネントを作成し、Page コンポーネントの中で組み合わせてください。

  • Header<header> タグ内に <h1>マイサイト</h1> を表示する
  • MainContent<main> タグ内に <p>ここがメインコンテンツです。</p> を表示する
  • Footer<footer> タグ内に <p>© 2025 マイサイト</p> を表示する
  • Page:上記3つを <div className="page"> の中に順番に並べる

【期待する表示】

マイサイト            ← Header
ここがメインコンテンツです。 ← MainContent
© 2025 マイサイト    ← Footer

🟡 中級問題(問題4〜9)

コンポーネント内でのデータ定義・条件分岐・リスト表示・クラス型コンポーネントの書き方を練習します。

問題4:コンポーネント内でデータを定義して表示しよう

【問題】
以下の条件をすべて満たす関数型コンポーネント ProductCard を作成してください。

  • コンポーネント内で以下のオブジェクトを定義する
const product = {
  name: "ワイヤレスキーボード",
  price: 8800,
  rating: 4,
  inStock: true,
};
  • 商品名を <h2>、価格を <p> で表示する(価格は toLocaleString() でカンマ区切り)
  • 評価(rating)の数だけ ⭐ を並べて表示する(Array(rating).fill("⭐").join("") を使うこと)
  • 在庫あり(inStock: true)なら「在庫あり」、なしなら「在庫切れ」と表示する(三項演算子を使うこと)

【期待する表示】

ワイヤレスキーボード
¥8,800
⭐⭐⭐⭐
在庫あり

問題5:リストをコンポーネントで表示しよう

【問題】
以下のデータを使い、メニューリストを表示するコンポーネント MenuList を作成してください。

const menuItems = [
  { id: 1, label: "ホーム",          href: "#home"     },
  { id: 2, label: "サービス",        href: "#services" },
  { id: 3, label: "ポートフォリオ",  href: "#portfolio"},
  { id: 4, label: "お問い合わせ",    href: "#contact"  },
];
  • <nav> タグの中に <ul> を配置し、map() で各アイテムを <li> としてレンダリングする
  • <li> の中に <a href={item.href}>{item.label}</a> を配置する
  • <li> には key 属性として id を使うこと

【期待する表示】

• ホーム
• サービス
• ポートフォリオ
• お問い合わせ

問題6:関数型とクラス型で同じUIを書き比べよう

【問題】
「ユーザーの名前と職業を表示するカード」を、関数型とクラス型の両方で実装してください。どちらも同じ表示になることを確認してください。

  • コンポーネント内で name = "佐藤花子"job = "デザイナー" を定義して表示する
  • 関数型コンポーネントの名前:UserCardFunction
  • クラス型コンポーネントの名前:UserCardClass

【期待する表示(両方同じ)】

佐藤花子
デザイナー

問題7:クラス型コンポーネントでstateを使おう

【問題】
以下の条件をすべて満たすクラス型コンポーネント LikeButton を作成してください。

  • state として likes(初期値: 0)を持つ
  • ボタンをクリックするたびに likes が1増える
  • ボタンのテキストは "❤️ いいね!(〇件)" と表示する(〇には現在の likes の値が入る)
  • this.setState() を使って状態を更新すること

【期待する表示(初期状態)】

❤️ いいね!(0件)

問題8:コンポーネントを適切に分割しよう

【問題】
以下の「分割前」コンポーネントを、適切な単位で3つのコンポーネントに分割してください。

// 分割前(すべてが1つにまとまっている)
const BlogPost = () => (
  <article>
    <div className="post-header">
      <h1>Reactを学ぼう</h1>
      <p>投稿日: 2025年8月7日</p>
    </div>
    <div className="post-body">
      <p>Reactはコンポーネントベースのライブラリです。</p>
      <p>再利用可能なUIを簡単に作成できます。</p>
    </div>
    <div className="post-footer">
      <span>👍 24</span>
      <span>💬 8件のコメント</span>
    </div>
  </article>
);
  • ヘッダー部分 → PostHeader コンポーネント
  • 本文部分 → PostBody コンポーネント
  • フッター部分 → PostFooter コンポーネント
  • 分割後の BlogPost は3つのコンポーネントを組み合わせるだけにする

【期待する表示】

Reactを学ぼう
投稿日: 2025年8月7日

Reactはコンポーネントベースのライブラリです。
再利用可能なUIを簡単に作成できます。

24
8件のコメント

問題9:コンポーネントのエラーを修正しよう

【問題】
以下のコードには5つの問題があります。すべて修正してください。

import React from 'react';

class productList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { items: ["りんご", "バナナ", "みかん"] };
  }

  render() {
    return (
      <div>
        <h2>商品リスト</h2>
        <ul>
          {this.state.items.map((item) => (
            <li>{item}</li>
          ))}
        </ul>
        <p>合計: {this.state.items.length}件
      </div>
    );
  }
}

【問題箇所のヒント】

  • クラス名がパスカルケースになっていない
  • map()key 属性が抜けている
  • <p> タグが閉じられていない
  • コンポーネントが export されていない(export default を追加すること)
  • アロー関数内の変数名 item が外側の変数と衝突しないか確認し、問題なければそのままでよい(これはヒントのダミーで実際はミスがない)

【期待する表示】

商品リスト

・りんご
・バナナ
・みかん

合計: 3件

🔴 上級問題(問題10〜12)

コンポーネントの設計・分割・状態管理など、実際の開発に近いシナリオに挑戦します。

問題10:プレゼンテーションとコンテナを分離しよう

【問題】
以下のデータと要件をもとに、データ処理を担当するコンテナコンポーネント表示だけを担当するプレゼンテーションコンポーネントに分けて実装してください。

const rawScores = [
  { id: 1, name: "田中太郎", score: 72 },
  { id: 2, name: "鈴木花子", score: 91 },
  { id: 3, name: "佐藤次郎", score: 58 },
  { id: 4, name: "山田三郎", score: 85 },
];
  • ScoreItem(プレゼンテーション):名前・スコア・合否を表示するだけのコンポーネント。60点以上を「合格」、未満を「不合格」と表示する
  • ScoreList(コンテナ)rawScoresfilter() で合格者のみに絞り込み、ScoreItem を使って表示する
  • ScoreItem に直接データを渡す際は、namescore を個別に渡すこと(propsの先取り練習)

【期待する表示(合格者のみ)】

鈴木花子 — 91点 — 合格
山田三郎 — 85点 — 合格
田中太郎 — 72点 — 合格

問題11:クラス型コンポーネントでカウンターを拡張しよう

【問題】
以下の条件をすべて満たすクラス型コンポーネント AdvancedCounter を作成してください。

  • statecount(初期値: 0)・history(初期値: [])の2つを持つ
  • 「+1」ボタン:count を1増やし、history"+ 1" を追加する
  • 「−1」ボタン:count を1減らし、history"- 1" を追加する
  • 「リセット」ボタン:count を0に戻し、history"リセット" を追加する
  • 現在の counthistory の内容(最新5件のみ)を表示する
  • history の更新には setState の関数形式(prevState =>)を使うこと

【期待する表示(+1を2回、−1を1回押した後)】

現在のカウント: 1
操作履歴:
• + 1
• + 1
• - 1

問題12:コンポーネントツリーを設計・実装しよう

【問題】
以下のデータを使って、ECサイトの商品一覧ページを想定したコンポーネントツリーを設計・実装してください。

const categories = [
  {
    id: 1,
    name: "電子機器",
    products: [
      { id: 101, name: "ノートPC",    price: 89800, inStock: true  },
      { id: 102, name: "スマートフォン", price: 64800, inStock: false },
    ],
  },
  {
    id: 2,
    name: "家具",
    products: [
      { id: 201, name: "デスクチェア", price: 45000, inStock: true  },
      { id: 202, name: "本棚",        price: 18000, inStock: true  },
    ],
  },
];
  • ProductCard:商品名・価格(カンマ区切り)・在庫状況(在庫あり/在庫切れ)を表示する。在庫切れのカードには className="out-of-stock" を追加する
  • CategorySection:カテゴリ名を <h2> で表示し、そのカテゴリの商品を ProductCard で並べる
  • ProductListPagecategoriesmap() で展開し、CategorySection を並べる。ページ上部に <h1>商品一覧</h1> を表示する
  • すべての map() に適切な key を設定すること

【期待する表示】

商品一覧

電子機器
  ノートPC — ¥89,800 — 在庫あり
  スマートフォン — ¥64,800 — 在庫切れ(out-of-stock クラス付き)

家具
  デスクチェア — ¥45,000 — 在庫あり
  本棚 — ¥18,000 — 在庫あり

📘 解説(全12問)

解説1:最初のコンポーネントを作ろう

【解答例】

// 関数宣言による書き方
function WelcomeCard() {
  return (
    <div>
      <h2>Reactへようこそ!</h2>
      <p>コンポーネントの学習を始めましょう。</p>
    </div>
  );
}

// アロー関数による書き方(動作は同じ)
const WelcomeCard = () => (
  <div>
    <h2>Reactへようこそ!</h2>
    <p>コンポーネントの学習を始めましょう。</p>
  </div>
);

【解説】

  • 関数宣言とアロー関数はどちらの書き方でもコンポーネントとして動作する
  • コンポーネント名は必ず大文字始まり(パスカルケース)にする
  • 複数行のJSXを返すときは () で囲み、1つのルート要素で包む

解説2:コンポーネントの命名ミスを修正しよう

【解答例】

// ✅ UserCard(大文字始まりに修正)
function UserCard() {
  return (
    <div>
      <h2>田中太郎</h2>
      <p>エンジニア</p>
    </div>
  );
}

// ✅ App(大文字始まりに修正)
const App = () => {
  return (
    <div>
      <UserCard />  {/* ✅ 大文字始まりに修正 */}
      <UserCard />  {/* ✅ 大文字始まりに修正 */}
    </div>
  );
};

【解説】

  • 修正箇所は4つ:userCardUserCard(定義)、appApp(定義)、<userCard /><UserCard />(使用箇所2つ)
  • 小文字始まりのタグはHTMLタグとして扱われるため、カスタムコンポーネントは認識されない
  • <userCard /> はブラウザに未知のHTMLタグとして送られ、意図した表示にならない

解説3:コンポーネントを組み合わせよう

【解答例】

const Header = () => (
  <header>
    <h1>マイサイト</h1>
  </header>
);

const MainContent = () => (
  <main>
    <p>ここがメインコンテンツです。</p>
  </main>
);

const Footer = () => (
  <footer>
    <p>&copy; 2025 マイサイト</p>
  </footer>
);

// 親コンポーネントで組み合わせる
const Page = () => (
  <div className="page">
    <Header />
    <MainContent />
    <Footer />
  </div>
);

【解説】

  • コンポーネントは <コンポーネント名 /> の形式でJSX内に配置できる
  • 親コンポーネントが子コンポーネントを呼び出す形で、ツリー状の構造が作られる
  • © などの特殊文字はJSX内で &copy; とエスケープするか、Unicode(©)を使う

解説4:コンポーネント内でデータを定義して表示しよう

【解答例】

const ProductCard = () => {
  const product = {
    name: "ワイヤレスキーボード",
    price: 8800,
    rating: 4,
    inStock: true,
  };

  return (
    <div className="product-card">
      <h2>{product.name}</h2>
      <p>¥{product.price.toLocaleString()}</p>
      <p>{Array(product.rating).fill("⭐").join("")}</p>
      <p>{product.inStock ? "在庫あり" : "在庫切れ"}</p>
    </div>
  );
};

【解説】

  • 関数型コンポーネントの return の前にJavaScriptの変数や計算を書ける
  • Array(n).fill("⭐").join(""):長さ n の配列を作り、⭐で埋めて文字列に結合する定番テクニック
  • toLocaleString() は数値を地域のフォーマット(日本なら 8,800 など)に変換する

解説5:リストをコンポーネントで表示しよう

【解答例】

const MenuList = () => (
  <nav>
    <ul>
      {menuItems.map((item) => (
        <li key={item.id}>
          <a href={item.href}>{item.label}</a>
        </li>
      ))}
    </ul>
  </nav>
);

【解説】

  • map() でリストを展開するパターンはReactで最頻出のテクニック
  • key はReactが各要素を識別するために必要。インデックスより一意なIDを使う方が安全
  • JSX内の href は文字列だが、変数を渡すときは href={item.href} のように {} を使う

解説6:関数型とクラス型で同じUIを書き比べよう

【解答例】

// 関数型コンポーネント
const UserCardFunction = () => {
  const name = "佐藤花子";
  const job  = "デザイナー";
  return (
    <div>
      <h2>{name}</h2>
      <p>{job}</p>
    </div>
  );
};

// クラス型コンポーネント
class UserCardClass extends React.Component {
  render() {
    const name = "佐藤花子";
    const job  = "デザイナー";
    return (
      <div>
        <h2>{name}</h2>
        <p>{job}</p>
      </div>
    );
  }
}

【解説】

  • 関数型もクラス型も、最終的に返すJSXは同じ形になる
  • クラス型では render() メソッドの中に変数定義やロジックを書く
  • 実際の開発では、新規コードは関数型で書くことを推奨。クラス型は既存コードを読む際に必要

解説7:クラス型コンポーネントでstateを使おう

【解答例】

class LikeButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = { likes: 0 };
  }

  render() {
    return (
      <button onClick={() => this.setState({ likes: this.state.likes + 1 })}>
        ❤️ いいね!({this.state.likes}件)
      </button>
    );
  }
}

【解説】

  • クラス型コンポーネントの状態は this.state で読み取り、this.setState() で更新する
  • onClick にはアロー関数 () => ... を渡す。直接関数呼び出し(onClick={this.handleClick()})では、レンダリング時に実行されてしまうので注意
  • setState() を呼ぶとコンポーネントが再レンダリングされ、表示が自動的に更新される

解説8:コンポーネントを適切に分割しよう

【解答例】

const PostHeader = () => (
  <div className="post-header">
    <h1>Reactを学ぼう</h1>
    <p>投稿日: 2025年8月7日</p>
  </div>
);

const PostBody = () => (
  <div className="post-body">
    <p>Reactはコンポーネントベースのライブラリです。</p>
    <p>再利用可能なUIを簡単に作成できます。</p>
  </div>
);

const PostFooter = () => (
  <div className="post-footer">
    <span>👍 24</span>
    <span>💬 8件のコメント</span>
  </div>
);

// 分割後のBlogPost:組み合わせるだけでシンプルになる
const BlogPost = () => (
  <article>
    <PostHeader />
    <PostBody />
    <PostFooter />
  </article>
);

【解説】

  • 分割後の BlogPost は「何を組み合わせているか」が一目でわかるようになった
  • 各コンポーネントが独立しているため、PostFooter だけスタイルを変えたいときも他に影響しない
  • コンポーネント名はその役割を表す名前にすることで、コードの可読性が上がる

解説9:コンポーネントのエラーを修正しよう

【解答例】

import React from 'react';

// ✅ クラス名をパスカルケースに修正
class ProductList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { items: ["りんご", "バナナ", "みかん"] };
  }

  render() {
    return (
      <div>
        <h2>商品リスト</h2>
        <ul>
          {this.state.items.map((item, index) => (
            <li key={index}>{item}</li>  {/* ✅ key属性を追加 */}
          ))}
        </ul>
        <p>合計: {this.state.items.length}件</p>  {/* ✅ </p> を追加 */}
      </div>
    );
  }
}

export default ProductList;  // ✅ export を追加

【解説】

  • 修正箇所は4つ:クラス名 productListProductList / key 属性の追加 / <p> タグの閉じ忘れ / export default の追加
  • タグの閉じ忘れはエラーメッセージが出にくい場合もあるため、常に意識して確認する習慣をつける
  • export default がないと他のファイルからインポートできない

解説10:プレゼンテーションとコンテナを分離しよう

【解答例】

// プレゼンテーションコンポーネント:表示だけを担当
const ScoreItem = ({ name, score }) => (
  <li>
    {name} — {score}点 — {score >= 60 ? "合格" : "不合格"}
  </li>
);

// コンテナコンポーネント:データ処理を担当
const ScoreList = () => {
  const passed = rawScores.filter((s) => s.score >= 60);

  return (
    <ul>
      {passed.map((s) => (
        <ScoreItem key={s.id} name={s.name} score={s.score} />
      ))}
    </ul>
  );
};

【解説】

  • ScoreItem({ name, score }) は「propsオブジェクトの分割代入」。次の記事で詳しく学ぶが、props.name と書くのと同じ意味
  • データのフィルタリングは ScoreList(コンテナ)側で行い、ScoreItem(プレゼンテーション)には表示に必要な値だけを渡す
  • この分離により ScoreItem は汎用的になり、別のデータソースでも再利用できる

解説11:クラス型コンポーネントでカウンターを拡張しよう

【解答例】

class AdvancedCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, history: [] };
  }

  // 操作を記録するヘルパーメソッド
  addHistory(label) {
    this.setState((prevState) => ({
      history: [...prevState.history, label],
    }));
  }

  render() {
    const { count, history } = this.state;
    const recentHistory = history.slice(-5); // 最新5件のみ

    return (
      <div>
        <p>現在のカウント: {count}</p>
        <button onClick={() => { this.setState({ count: count + 1 }); this.addHistory("+ 1"); }}>
          +1
        </button>
        <button onClick={() => { this.setState({ count: count - 1 }); this.addHistory("- 1"); }}>
          −1
        </button>
        <button onClick={() => { this.setState({ count: 0 }); this.addHistory("リセット"); }}>
          リセット
        </button>
        <ul>
          {recentHistory.map((entry, index) => (
            <li key={index}>{entry}</li>
          ))}
        </ul>
      </div>
    );
  }
}

【解説】

  • setState の関数形式 prevState => ({...}) は、直前の状態を確実に参照したいときに使う。配列を更新する際の定番パターン
  • slice(-5) は配列の末尾5件を取り出す。履歴が増えすぎないように最新件数のみ表示するのはよくある実装
  • クラス型では処理を addHistory() のようなメソッドに切り出してすっきりさせることができる

解説12:コンポーネントツリーを設計・実装しよう

【解答例】

// 最小単位:商品カード
const ProductCard = ({ product }) => (
  <div className={product.inStock ? "product-card" : "product-card out-of-stock"}>
    <span>{product.name}</span> —
    <span> ¥{product.price.toLocaleString()}</span> —
    <span> {product.inStock ? "在庫あり" : "在庫切れ"}</span>
  </div>
);

// カテゴリセクション:ProductCard を束ねる
const CategorySection = ({ category }) => (
  <section>
    <h2>{category.name}</h2>
    {category.products.map((product) => (
      <ProductCard key={product.id} product={product} />
    ))}
  </section>
);

// ページ全体:CategorySection を束ねる
const ProductListPage = () => (
  <div>
    <h1>商品一覧</h1>
    {categories.map((category) => (
      <CategorySection key={category.id} category={category} />
    ))}
  </div>
);

【解説】

  • コンポーネントツリーの設計は「小さい単位(ProductCard)→ 中間単位(CategorySection)→ 全体(ProductListPage)」の順に考えるとわかりやすい
  • { product }{ category } は問題10と同様のpropsの分割代入。詳しくは次の記事で解説する
  • map()key を設定する際は、そのレベルの一意IDを使う(カテゴリには category.id、商品には product.id