― 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人分の情報を描画する最小単位
これにより、それぞれのコンポーネントは**「何をするか」がはっきりした小さな責任を持つ**ようになり、アプリ全体が見通しやすくなります。
よくあるミスと注意点
props
の受け取り忘れ:コンポーネントの引数に注意(function Component({ prop })
)key
の未指定:.map()
で表示する要素には必ずkey
を- 状態を下位に持たせてしまう:状態はできるだけ「共通の親」に置く(リフトアップ)
Reactでは、「どこに状態を持つか」が設計の肝になります。最も上の共通の親に状態を置き、子にpropsとして渡すのが基本です。
まとめ
今回は、Reactアプリを構造的に整理するためのコンポーネント設計の基本を実践しました。
- UIの役割に応じて関心を分離する
- 状態管理は親コンポーネント、表示は子コンポーネントへ
- 再利用しやすく、テストしやすく、保守しやすい構造になる
今後のReact開発では、アプリの規模に応じたコンポーネントの分割戦略が非常に重要になります。
次回は、React開発で避けて通れない「スタイル設計」について取り上げ、CSS Modulesやstyled-componentsなどのモダンなスタイリング手法を紹介していきます。
次回予告
Reactのスタイル設計 ― CSS Modulesとstyled-componentsの実践比較
コメント