メインコンテンツまでスキップ

条件分岐とリスト表示

この章では、条件に応じたレンダリングと、配列データのリスト表示を学びます。

この章で学ぶこと

  • 条件分岐の複数の方法(if文、三項演算子、&&演算子)
  • mapを使ったリスト表示
  • key属性の重要性
  • フィルタリングと空配列の処理
備考

この章では03章で作成したプロジェクトを使用します。npm run devで開発サーバーを起動し、コードを書きながら学習を進めましょう。

条件分岐

条件分岐には複数の方法があります。※三項演算子や論理演算子については02章で解説しています。

if文による条件分岐

function Greeting({ isLoggedIn }: { isLoggedIn: boolean }) {
if (isLoggedIn) {
return <h1>おかえりなさい!</h1>
}
return <h1>ログインしてください</h1>
}

三項演算子

function Greeting({ isLoggedIn }: { isLoggedIn: boolean }) {
return (
<h1>
{isLoggedIn ? 'おかえりなさい!' : 'ログインしてください'}
</h1>
)
}

AND演算子(&&)

条件がtrueの場合のみ表示するときに使えます。

function Notification({ count }: { count: number }) {
return (
<div>
{/* && 演算子: 左辺が true のときだけ右辺を表示
count > 0 が true → <span>新着{count}件</span> を表示
count > 0 が false → 何も表示しない */}
{count > 0 && <span>新着{count}</span>}
</div>
)
}
警告

&&演算子で0を使うと、0がそのまま表示されることがあります。数値を条件にする場合は、明示的にbooleanに変換しましょう。

// 問題: countが0のとき、"0"が表示される
{count && <span>{count}</span>}

// 解決: 明示的にbooleanに変換
{count > 0 && <span>{count}</span>}

試してみよう: ログイン状態の表示

isLoggedInというstateを持ち、trueなら「ようこそ!」、falseなら「ログインしてください」と表示するコンポーネントを作ってみましょう。

ヒント
  1. useState<boolean>(false)でログイン状態を管理
  2. 三項演算子で表示を切り替え
  3. ボタンでログイン/ログアウトを切り替え
回答と解説
import { useState } from 'react'
import './App.css'

function LoginStatus() {
const [isLoggedIn, setIsLoggedIn] = useState(false)

const handleToggleLogin = () => {
setIsLoggedIn(!isLoggedIn)
}

return (
<div>
<h1>{isLoggedIn ? 'ようこそ!' : 'ログインしてください'}</h1>
<button onClick={handleToggleLogin}>
{isLoggedIn ? 'ログアウト' : 'ログイン'}
</button>
</div>
)
}

export default function App() {
return <LoginStatus />
}

三項演算子を使って、isLoggedInの値に応じて異なるメッセージを表示しています。ボタンのラベルも同様に切り替えることで、現在の状態がわかりやすくなります。

OR演算子とNullish Coalescing

フォールバック値を設定する場合に使用します。

function Welcome({ name }: { name?: string }) {
return <h1>こんにちは、{name || 'ゲスト'}さん!</h1>
}

// ?? は null/undefined のみフォールバック
function Display({ value }: { value?: number }) {
return <p>値: {value ?? '未設定'}</p>
}

複数条件の分岐

早期リターン

function Status({ status }: { status: string }) {
if (status === 'loading') {
return <p>読み込み中...</p>
}

if (status === 'error') {
return <p>エラーが発生しました</p>
}

return <p>完了</p>
}

オブジェクトマッピング

type Status = 'pending' | 'approved' | 'rejected'

// as const を付けることで、各プロパティの型が文字列リテラル型に固定される
const statusConfig = {
pending: { label: '審査中', color: 'orange' },
approved: { label: '承認済', color: 'green' },
rejected: { label: '却下', color: 'red' },
} as const satisfies Record<Status, { label: string; color: string }>

function StatusBadge({ status }: { status: Status }) {
// status の値('pending' など)をキーにして設定を取得
// Status が statusConfig のキー集合と一致することを satisfies で保証
const config = statusConfig[status]

return (
<span style={{ color: config.color }}>
{config.label}
</span>
)
}
備考

as const satisfies Record<Status, ...> という書き方は、**「型を満たしていることを検査しつつ、リテラル型情報は保持する」**という TypeScript のパターンです。Status に値を追加したとき、statusConfig にキーを追加し忘れるとコンパイルエラーになります。as const がないと colorstring 型に広がってしまうため、両方を組み合わせるのがコツです。

リスト表示

mapを使ったリスト表示

配列データはmap()メソッドで要素に変換します。※mapメソッドについては02章で解説しています。

function FruitList() {
const fruits = ['りんご', 'バナナ', 'オレンジ']

return (
<ul>
{/* map() で配列の各要素をJSXに変換
fruit => (...) は各要素に対して実行される関数
key={fruit} で各要素を一意に識別(Reactの更新最適化に必要) */}
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
)
}

key属性

リストの各要素には一意のkey属性が必要です。

なぜkeyが必要なのか?

Reactはkeyを使って、どの要素が変更・追加・削除されたかを識別します。適切なkeyがないと、Reactは効率的な更新ができず、パフォーマンスが低下したり、予期しないバグが発生したりします。

// keyがあると、Reactは「id=2の要素だけ変更」と認識できる
[
{ id: 1, name: "A" }, // 変更なし
{ id: 2, name: "B'" }, // 変更あり
{ id: 3, name: "C" } // 変更なし
]
type User = {
id: number
name: string
}

function UserList({ users }: { users: User[] }) {
return (
<ul>
{/* key には一意の識別子(id)を使用
インデックス(0, 1, 2...)は要素の追加・削除時に問題を起こす可能性がある */}
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
警告

keyにはインデックスではなく、一意のIDを使用しましょう。インデックスを使うと、要素の追加・削除時に予期しない動作が起こる可能性があります。

// 非推奨: インデックスをkeyに使用
{items.map((item, index) => (
<li key={index}>{item}</li>
))}

// 推奨: 一意のIDをkeyに使用
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}

試してみよう: 商品リストの表示

商品名と価格を持つ配列をmapで表示し、適切なkeyを設定してみましょう。

ヒント
  1. idnamepriceを持つ型を定義
  2. mapで配列を要素に変換
  3. keyにはidを使用(インデックスは避ける)
回答と解説
import './App.css'

type Product = {
id: number
name: string
price: number
}

function ProductList() {
const products: Product[] = [
{ id: 1, name: 'りんご', price: 150 },
{ id: 2, name: 'バナナ', price: 100 },
{ id: 3, name: 'オレンジ', price: 200 },
{ id: 4, name: 'ぶどう', price: 350 },
{ id: 5, name: 'メロン', price: 1500 }
]

return (
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name}: {product.price}
</li>
))}
</ul>
)
}

export default function App() {
return <ProductList />
}

keyにはインデックスではなく、一意のIDであるproduct.idを使用しています。これにより、要素の追加・削除時にReactが効率的に更新できます。

フィルタリングとマッピング

type Task = {
id: number
title: string
completed: boolean
}

function TaskList({ tasks }: { tasks: Task[] }) {
// filter() で条件に合う要素だけを抽出
// !task.completed → 完了していないタスクのみ残す
const incompleteTasks = tasks.filter(task => !task.completed)

return (
<ul>
{/* filter した結果を map で表示 */}
{incompleteTasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
)
}

空配列の処理

function ItemList({ items }: { items: string[] }) {
// 早期リターン: 配列が空の場合は専用のUIを返す
// これにより、以降のコードは「配列が空でない」前提で書ける
if (items.length === 0) {
return <p>アイテムがありません</p>
}

return (
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
)
}

試してみよう: フィルタリング

商品リストに「1000円以上のみ表示」というチェックボックスを追加し、フィルタリングを実装してみましょう。

ヒント
  1. useState<boolean>(false)でフィルタ状態を管理
  2. filterで条件に合う商品を抽出
  3. mapでフィルタ結果を表示
  4. フィルタ結果が空の場合のフォールバックUIも実装
回答と解説
import { useState } from 'react'
import './App.css'

type Product = {
id: number
name: string
price: number
}

function FilteredProductList() {
const [filterExpensive, setFilterExpensive] = useState(false)

const products: Product[] = [
{ id: 1, name: 'りんご', price: 150 },
{ id: 2, name: 'バナナ', price: 100 },
{ id: 3, name: 'オレンジ', price: 200 },
{ id: 4, name: 'ぶどう', price: 350 },
{ id: 5, name: 'メロン', price: 1500 }
]

const handleFilterChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFilterExpensive(e.target.checked)
}

// 三項演算子で表示する商品を切り替え
// filterExpensive が true → 1000円以上のみ
// filterExpensive が false → 全商品
const filteredProducts = filterExpensive
? products.filter(product => product.price >= 1000)
: products

return (
<div>
<label>
<input
type="checkbox"
checked={filterExpensive}
onChange={handleFilterChange}
/>
1000円以上のみ表示
</label>
{filteredProducts.length === 0 ? (
<p>該当する商品がありません</p>
) : (
<ul>
{filteredProducts.map((product) => (
<li key={product.id}>
{product.name}: {product.price}
</li>
))}
</ul>
)}
</div>
)
}

export default function App() {
return <FilteredProductList />
}

filterメソッドで条件に合う商品だけを抽出し、その結果をmapで表示しています。フィルタ結果が空の場合のフォールバックUIも実装しています。

まとめ

  • 条件分岐: if文、三項演算子、&&||??
  • リスト: map()で配列を要素に変換
  • key属性: 一意のIDを指定(インデックスは避ける)
  • フィルタリング: filter()と組み合わせる
  • 空配列: 適切なフォールバックUIを表示

次の章では、フォームの扱い方について学びます。

次に読む