リストと条件分岐
配列データをリスト表示する方法と、条件に応じてUIを切り替える方法を学びます。現実のアプリに欠かせないパターンです。
このチャプターで学ぶこと
- .map()を使って配列からJSXのリストを生成できる
- keyプロップの役割を理解して適切に設定できる
- &&演算子、三項演算子、アーリーリターンで条件分岐ができる
- リストと条件分岐を組み合わせてUIを作れる
- シンプルなTodoリスト(追加・完了切り替え)を自力で作れる
リストと条件分岐
データベースから取得したユーザー一覧を表示したい、ステータスによって表示を変えたい、そんな場面は毎日あります。このチャプターではReactでのリスト表示と条件分岐を学びましょう。
配列を .map() でレンダリングする
PHPのforeachに相当するのが、JavaScriptの .map() です。
<!-- Blade (PHP) での書き方 -->
@foreach($users as $user)
<li>{{ $user->name }}</li>
@endforeach
// React (JSX) での書き方
const users = ['田中', '鈴木', '佐藤'];
function UserList() {
return (
<ul>
{users.map((user) => (
<li>{user}</li>
))}
</ul>
);
}
.map() は配列の各要素を変換して、新しい配列を返します。ここではそれぞれの名前を <li> 要素に変換しています。
.map() はJavaScriptの配列メソッドです。各要素を受け取り、返した値で新しい配列を作ります。
const numbers = [1, 2, 3];
const doubled = numbers.map((n) => n * 2);
// doubled は [2, 4, 6]ReactではJSX要素の配列を返すために使います。
オブジェクトの配列を扱う
実際のアプリではオブジェクトの配列を扱うことがほとんどです。
import { useState } from 'react';
// ユーザーの配列(APIから取得した想定)
const users = [
{ id: 1, name: '田中太郎', email: 'tanaka@example.com', role: '管理者' },
{ id: 2, name: '鈴木花子', email: 'suzuki@example.com', role: '一般' },
{ id: 3, name: '佐藤次郎', email: 'sato@example.com', role: '一般' },
];
function UserList() {
return (
<ul>
{users.map((user) => (
<li key={user.id}>
<strong>{user.name}</strong>
<span> ({user.role})</span>
<br />
<small>{user.email}</small>
</li>
))}
</ul>
);
}
key プロップ:なぜ必要か
上のコードを見ると key={user.id} が付いています。これは必須です。付けないとコンソールに警告が出ます。
Warning: Each child in a list should have a unique "key" prop.
key が必要な理由
Reactはリストの中のどの要素が変更・追加・削除されたかを効率よく把握するために key を使います。
例えば、リストに新しいアイテムが追加されたとき:
変更前: [A, B, C]
変更後: [A, B, C, D] ← D が末尾に追加された
key があれば「Aは変わってない、Bも、Cも、Dが新しい!」と素早く判断できます。
key がないと、Reactはリスト全体を再描画しようとして、パフォーマンスが低下します。また、inputのフォーカスやアニメーション状態が予期せずリセットされることもあります。
key には何を使うべきか
{/* 良い例: DBのIDなど一意の値 */}
{items.map((item) => (
<Item key={item.id} {...item} />
))}
{/* 良い例: 一意の文字列 */}
{tags.map((tag) => (
<Tag key={tag.slug} name={tag.name} />
))}
{/* 悪い例: インデックスは避けるべき(順序が変わると問題が起きる) */}
{items.map((item, index) => (
<Item key={index} {...item} /> // リストの順序が変わらない場合のみ許容
))}
例えば [A, B, C] の配列があり、A を削除すると [B, C] になります。
- インデックスをkeyにすると: B が key=0、C が key=1 になる(変更前はB=1、C=2だった)
- ReactはBとCが別物に変わったと誤解して余分な再描画が起きる
DBから取得したデータなら id を使えばほぼ問題ありません。
key はコンポーネントの外側の要素に付ける
// OK: .map() が返す一番外側の要素に key を付ける
{items.map((item) => (
<div key={item.id}>
<h3>{item.title}</h3>
<p>{item.body}</p>
</div>
))}
// NG: 内側の要素に付けても意味がない
{items.map((item) => (
<div>
<h3 key={item.id}>{item.title}</h3> {/* これは間違い */}
<p>{item.body}</p>
</div>
))}
条件分岐: && 演算子
「ある条件のときだけ表示する」ときによく使います。
function Notification({ count }) {
return (
<div>
<h1>ダッシュボード</h1>
{/* count が 0 より大きいときだけ表示 */}
{count > 0 && (
<div className="badge">
{count}件の通知があります
</div>
)}
</div>
);
}
A && B は「A が truthy なら B を返す、falsy なら A を返す」という JavaScript の動作を利用しています。
count && <p>{count}件</p> と書くと、count が 0 のとき画面に 0 と表示されてしまいます。
// 危険: count が 0 だと "0" が表示される
{count && <Badge count={count} />}
// 安全: 明示的に真偽値に変換する
{count > 0 && <Badge count={count} />}
{!!count && <Badge count={count} />} 条件分岐: 三項演算子
「条件によってAかBを表示する」ときに使います。
function StatusBadge({ isActive }) {
return (
<span style={{ color: isActive ? 'green' : 'gray' }}>
{isActive ? '稼働中' : '停止中'}
</span>
);
}
import { useState } from 'react';
function LoginStatus() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
{isLoggedIn ? (
// ログイン中の表示
<div>
<p>ようこそ!</p>
<button onClick={() => setIsLoggedIn(false)}>ログアウト</button>
</div>
) : (
// 未ログインの表示
<div>
<p>ログインしてください</p>
<button onClick={() => setIsLoggedIn(true)}>ログイン</button>
</div>
)}
</div>
);
}
条件分岐: アーリーリターン
コンポーネント自体の表示・非表示を制御したいときや、ローディング状態を処理するときはアーリーリターンが便利です。
function UserProfile({ user, isLoading }) {
// ローディング中はスピナーを返す
if (isLoading) {
return <p>読み込み中...</p>;
}
// ユーザーデータがない場合
if (!user) {
return <p>ユーザーが見つかりません</p>;
}
// 正常な場合の表示
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
- 単純な表示・非表示 →
&&演算子 - AかBかを選ぶ → 三項演算子
- 複数の状態を処理する(ローディング、エラー、正常など) → アーリーリターン
三項演算子はネストすると読みにくくなるため、3パターン以上になる場合はアーリーリターンか、別の変数に条件分岐の結果を入れる方法を使いましょう。
リストと条件分岐の組み合わせ
実際のアプリでは、リストの中で条件分岐することがよくあります。
import { useState } from 'react';
const products = [
{ id: 1, name: 'Tシャツ', price: 2000, inStock: true },
{ id: 2, name: 'パーカー', price: 5000, inStock: false },
{ id: 3, name: 'ジーンズ', price: 8000, inStock: true },
{ id: 4, name: 'キャップ', price: 3000, inStock: false },
];
function ProductList() {
const [showInStockOnly, setShowInStockOnly] = useState(false);
// フィルタリング
const displayProducts = showInStockOnly
? products.filter((p) => p.inStock)
: products;
return (
<div>
<label>
<input
type="checkbox"
checked={showInStockOnly}
onChange={(e) => setShowInStockOnly(e.target.checked)}
/>
在庫ありのみ表示
</label>
{/* 商品がない場合のメッセージ */}
{displayProducts.length === 0 && (
<p>該当する商品がありません</p>
)}
<ul>
{displayProducts.map((product) => (
<li key={product.id}>
<strong>{product.name}</strong>
<span> ¥{product.price.toLocaleString()}</span>
{/* 在庫切れの場合はバッジを表示 */}
{!product.inStock && (
<span style={{ color: 'red', marginLeft: '8px' }}>在庫切れ</span>
)}
</li>
))}
</ul>
</div>
);
}
演習: シンプルなTodoリストを作ろう
アイテムの追加と完了チェックができるTodoリストを作りましょう。
要件:
- テキストフィールドと「追加」ボタンでTodoを追加できる
- 各Todoにはチェックボックスがあり、クリックで完了・未完了を切り替えられる
- 完了したTodoは打ち消し線で表示する
- 入力フィールドが空のときは追加できない
データの形:
// Todo アイテムのデータ構造
const [todos, setTodos] = useState([
{ id: 1, text: 'Reactを学ぶ', completed: false },
{ id: 2, text: 'Todoリストを作る', completed: false },
]);ヒント:
import { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Reactを学ぶ', completed: false },
]);
const [inputText, setInputText] = useState('');
function handleAdd() {
if (!inputText.trim()) return; // 空のときは追加しない
const newTodo = {
id: Date.now(), // 簡易的なユニークID
text: inputText,
completed: false,
};
// 既存のTodoに新しいものを追加した新しい配列を作る
setTodos([...todos, newTodo]);
setInputText(''); // 入力欄をリセット
}
function handleToggle(id) {
// 対象のTodoのcompletedだけ反転させた新しい配列を作る
setTodos(todos.map((todo) =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
}
return (
<div>
<div>
<input
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="新しいタスク"
/>
<button onClick={handleAdd}>追加</button>
</div>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => handleToggle(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
</li>
))}
</ul>
</div>
);
}チャレンジ:
- 「削除」ボタンを各Todoに追加しよう(
filter()を使う) - 完了したTodoの数と全体のTodo数を表示しよう(例:「3/5 完了」)
- Enter キーでも追加できるようにしよう(
onKeyDownを使う)
Reactでは Stateを直接変更してはいけないルールがあります。配列を操作するときは、元の配列を変えずに新しい配列を作るのがポイントです。
// NG: 元の配列を直接変更している
todos.push(newTodo);
setTodos(todos); // これは再レンダリングされない!
// OK: スプレッド構文で新しい配列を作る
setTodos([...todos, newTodo]);
// 削除も同様
setTodos(todos.filter((todo) => todo.id !== targetId)); まとめ
.map()で配列からJSX要素のリストを生成するkeyプロップは一意の値(DBのIDなど)を設定する必須項目&&演算子:条件が真のときだけ表示- 三項演算子
? ::AかBかを切り替える - アーリーリターン:複数の状態(ローディング、エラーなど)を処理するのに便利
- Stateで配列を管理するときは、新しい配列を作って渡す
次のチャプターでは useEffect を学びます。APIからデータを取得したり、タイマーを設定したりと、コンポーネントの「外の世界」と連携する方法を学びましょう。