React レッスン8
リスト表示とKey
配列データの繰り返し描画とKeyプロパティを理解しよう
リスト表示の基本
Reactでは、配列の map() メソッドを使って データの一覧を表示します。これはReactで最も頻繁に使うパターンの1つです。
function FruitList() {
const fruits = ["りんご", "バナナ", "みかん", "ぶどう", "メロン"];
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
);
}
// オブジェクトの配列を表示
function UserList() {
const users = [
{ id: 1, name: "田中太郎", age: 25, role: "エンジニア" },
{ id: 2, name: "鈴木花子", age: 30, role: "デザイナー" },
{ id: 3, name: "佐藤次郎", age: 22, role: "マーケター" },
];
return (
<div>
<h2>ユーザー一覧</h2>
{users.map(user => (
<div key={user.id} style={{
padding: "12px",
margin: "8px 0",
border: "1px solid #ddd",
borderRadius: "8px",
}}>
<h3>{user.name}</h3>
<p>{user.age}歳 — {user.role}</p>
</div>
))}
</div>
);
}Keyプロパティの重要性
key は、Reactがリスト内の各要素を 識別するために必要な特別なプロパティです。正しいkeyを指定することで、 Reactが効率的にDOMを更新できます。
// NG: indexをkeyに使う(並び替え・削除時にバグの原因)
function BadExample() {
const [items, setItems] = useState(["A", "B", "C"]);
return (
<ul>
{items.map((item, index) => (
// indexは要素が入れ替わると対応が変わってしまう
<li key={index}>{item}</li>
))}
</ul>
);
}
// OK: ユニークなIDをkeyに使う
function GoodExample() {
const [todos, setTodos] = useState([
{ id: "abc123", text: "買い物" },
{ id: "def456", text: "掃除" },
{ id: "ghi789", text: "勉強" },
]);
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<ul>
{todos.map(todo => (
// ユニークなIDならReactが正しく追跡できる
<li key={todo.id}>
{todo.text}
<button onClick={() => deleteTodo(todo.id)}>削除</button>
</li>
))}
</ul>
);
}
// Keyのルール:
// 1. 兄弟要素間でユニークであること
// 2. 変化しない値を使うこと(ランダム値はNG)
// 3. データにIDがない場合はcrypto.randomUUID()で生成
// 4. indexは最後の手段(静的なリストのみ)リストのフィルタリング
filter() とmap() を組み合わせて、 条件に合うデータだけを表示できます。
import { useState } from "react";
function FilterableProductList() {
const products = [
{ id: 1, name: "ノートPC", category: "電子機器", price: 120000 },
{ id: 2, name: "マウス", category: "電子機器", price: 3000 },
{ id: 3, name: "Tシャツ", category: "衣類", price: 2500 },
{ id: 4, name: "コーヒー豆", category: "食品", price: 1500 },
{ id: 5, name: "キーボード", category: "電子機器", price: 8000 },
{ id: 6, name: "ジーンズ", category: "衣類", price: 6000 },
];
const [searchText, setSearchText] = useState("");
const [selectedCategory, setSelectedCategory] = useState("all");
// カテゴリ一覧を取得
const categories = [...new Set(products.map(p => p.category))];
// フィルタリング
const filteredProducts = products
.filter(product => {
const matchesSearch = product.name
.toLowerCase()
.includes(searchText.toLowerCase());
const matchesCategory =
selectedCategory === "all" || product.category === selectedCategory;
return matchesSearch && matchesCategory;
});
return (
<div>
<input
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="商品を検索..."
/>
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
>
<option value="all">すべて</option>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
<p>{filteredProducts.length}件の商品</p>
{filteredProducts.length === 0 ? (
<p>該当する商品がありません</p>
) : (
<ul>
{filteredProducts.map(product => (
<li key={product.id}>
{product.name} — {product.price.toLocaleString()}円
<span>({product.category})</span>
</li>
))}
</ul>
)}
</div>
);
}リストのソート
表示前にデータをソートすることで、並び替え機能を実装できます。 元の配列を変更しないよう、スプレッド構文でコピーしてからソートします。
import { useState, useMemo } from "react";
function SortableTable() {
const data = [
{ id: 1, name: "佐藤", score: 85, date: "2025-01-15" },
{ id: 2, name: "田中", score: 92, date: "2025-01-10" },
{ id: 3, name: "鈴木", score: 78, date: "2025-01-20" },
{ id: 4, name: "山田", score: 95, date: "2025-01-05" },
];
const [sortKey, setSortKey] = useState("name");
const [sortOrder, setSortOrder] = useState("asc");
// useMemoで計算結果をキャッシュ
const sortedData = useMemo(() => {
return [...data].sort((a, b) => {
let comparison = 0;
if (sortKey === "name") {
comparison = a.name.localeCompare(b.name, "ja");
} else if (sortKey === "score") {
comparison = a.score - b.score;
} else if (sortKey === "date") {
comparison = new Date(a.date) - new Date(b.date);
}
return sortOrder === "asc" ? comparison : -comparison;
});
}, [sortKey, sortOrder]);
const handleSort = (key) => {
if (sortKey === key) {
// 同じキーなら順序を反転
setSortOrder(prev => prev === "asc" ? "desc" : "asc");
} else {
setSortKey(key);
setSortOrder("asc");
}
};
return (
<table>
<thead>
<tr>
<th onClick={() => handleSort("name")} style={{ cursor: "pointer" }}>
名前 {sortKey === "name" && (sortOrder === "asc" ? "▲" : "▼")}
</th>
<th onClick={() => handleSort("score")} style={{ cursor: "pointer" }}>
点数 {sortKey === "score" && (sortOrder === "asc" ? "▲" : "▼")}
</th>
<th onClick={() => handleSort("date")} style={{ cursor: "pointer" }}>
日付 {sortKey === "date" && (sortOrder === "asc" ? "▲" : "▼")}
</th>
</tr>
</thead>
<tbody>
{sortedData.map(row => (
<tr key={row.id}>
<td>{row.name}</td>
<td>{row.score}</td>
<td>{row.date}</td>
</tr>
))}
</tbody>
</table>
);
}リストアイテムをコンポーネントに分離
リストの各アイテムが複雑になる場合は、別のコンポーネントに分離すると管理しやすくなります。
// アイテムコンポーネント
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li style={{
display: "flex",
alignItems: "center",
gap: "8px",
padding: "8px",
borderBottom: "1px solid #eee",
}}>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span style={{
textDecoration: todo.done ? "line-through" : "none",
flex: 1,
}}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>削除</button>
</li>
);
}
// リストコンポーネント
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "Reactを学ぶ", done: false },
{ id: 2, text: "アプリを作る", done: false },
{ id: 3, text: "デプロイする", done: true },
]);
const handleToggle = (id) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
};
const handleDelete = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
return (
<div>
<h2>TODO ({todos.filter(t => !t.done).length}件未完了)</h2>
<ul style={{ listStyle: "none", padding: 0 }}>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
</div>
);
}まとめ
map()でデータ配列をJSX要素に変換するkeyはユニークで安定した値を使う(indexは最後の手段)filter()と組み合わせて検索・フィルタリングを実装- スプレッド構文 +
sort()でソート機能を実装 - 複雑なリストアイテムはコンポーネントに分離する