Reactのコンポーネント設計

― UIを分割・整理して再利用性を高める方法


はじめに

前回は、Reactを使ってユーザー情報を取得・表示し、検索で絞り込むアプリを構築しました。すべてのコードはAppコンポーネント内に収められていましたが、機能が増えてくると1つのコンポーネントにすべてを詰め込むのは保守や再利用の面で不利になります。

そこで重要になるのが、コンポーネントの分割と設計です。

Reactでは、UIの各パーツを独立したコンポーネントとして定義することで、次のようなメリットが得られます:

  • 再利用性:同じUIを他の場所でも使える
  • 保守性:関心の分離により、修正が局所的で済む
  • 可読性:1つのコンポーネントがシンプルになる

今回は、前回のアプリをベースに、検索バー・ユーザー一覧・ユーザーカードの3つのコンポーネントに分割し、Reactらしいアーキテクチャに近づけていきます。


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

まず、UIの役割ごとに機能を整理します:

  • App.jsx:全体の状態とデータ取得を管理(親コンポーネント)
  • SearchBar.jsx:検索入力を受け取り、状態を更新(子)
  • UserList.jsx:ユーザーの配列を受け取り、リスト表示(子)
  • UserCard.jsx:1人分のユーザー情報を表示(孫)

このように、「データを持つ親」と「表示するだけの子」の構造にすることで、状態と表示の責任を明確に分離できます。


ファイル構成例

/src/
├── App.jsx
├── components/
│   ├── SearchBar.jsx
│   ├── UserList.jsx
│   └── UserCard.jsx
├── App.css

各コンポーネントの実装

App.jsx

import { useState, useEffect } from 'react';
import './App.css';
import SearchBar from './components/SearchBar';
import UserList from './components/UserList';

function App() {
  const [users, setUsers] = useState([]);
  const [search, setSearch] = useState('');

  useEffect(() => {
    fetch('https://randomuser.me/api/?results=20')
      .then(res => res.json())
      .then(data => setUsers(data.results))
      .catch(err => console.error('取得エラー:', err));
  }, []);

  const filtered = users.filter(user => {
    const name = `${user.name.first} ${user.name.last}`.toLowerCase();
    return name.includes(search.toLowerCase());
  });

  return (
    <div className="app">
      <h1>ユーザー検索</h1>
      <SearchBar value={search} onChange={setSearch} />
      <UserList users={filtered} />
    </div>
  );
}

export default App;

components/SearchBar.jsx

function SearchBar({ value, onChange }) {
  return (
    <input
      type="text"
      placeholder="名前で検索"
      value={value}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

export default SearchBar;

components/UserList.jsx

import UserCard from './UserCard';

function UserList({ users }) {
  if (users.length === 0) {
    return <p>該当するユーザーが見つかりません。</p>;
  }

  return (
    <ul className="user-list">
      {users.map((user, index) => (
        <UserCard key={index} user={user} />
      ))}
    </ul>
  );
}

export default UserList;

components/UserCard.jsx

function UserCard({ user }) {
  return (
    <li className="user-card">
      <img src={user.picture.medium} alt="写真" />
      <div>
        <strong>{user.name.first} {user.name.last}</strong><br />
        <small>{user.email}</small>
      </div>
    </li>
  );
}

export default UserCard;

解説:Reactにおける責任の分離

  • Appは「状態(search, users)」を管理し、子に渡す
  • SearchBarは状態の一部(検索キーワード)を編集するUI
  • UserListは受け取った配列をただ表示するだけ
  • UserCardは1人分の情報を描画する最小単位

これにより、それぞれのコンポーネントは**「何をするか」がはっきりした小さな責任を持つ**ようになり、アプリ全体が見通しやすくなります。

🔗 参考:Thinking in React(React公式)


よくあるミスと注意点

  • propsの受け取り忘れ:コンポーネントの引数に注意(function Component({ prop })
  • keyの未指定:.map()で表示する要素には必ずkey
  • 状態を下位に持たせてしまう:状態はできるだけ「共通の親」に置く(リフトアップ)

Reactでは、「どこに状態を持つか」が設計の肝になります。最も上の共通の親に状態を置き、子にpropsとして渡すのが基本です。


まとめ

今回は、Reactアプリを構造的に整理するためのコンポーネント設計の基本を実践しました。

  • UIの役割に応じて関心を分離する
  • 状態管理は親コンポーネント、表示は子コンポーネントへ
  • 再利用しやすく、テストしやすく、保守しやすい構造になる

今後のReact開発では、アプリの規模に応じたコンポーネントの分割戦略が非常に重要になります。
次回は、React開発で避けて通れない「スタイル設計」について取り上げ、CSS Modulesやstyled-componentsなどのモダンなスタイリング手法を紹介していきます。


次回予告

Reactのスタイル設計 ― CSS Modulesとstyled-componentsの実践比較

コメント

タイトルとURLをコピーしました