Day 5・状態の設計と再利用 — 大きくしても壊れない作り
コンポーネント分割の指針とバケツリレー問題
責務・再利用・見通しで部品を分ける指針を学び、propsのバケツリレー(prop drilling)とContextで解決できることを軽くつかむ。
どこで部品を分けるか — 3つの物差し
1つのコンポーネントに何もかも詰め込むと、長くて読めない・直せない塊になります。だからといって、やみくもに細かく割ると今度は追いかけるのが大変です。分割の判断には、いつも同じ3つの物差しを当てるとブレません。「責務(1つの部品は1つの仕事)」「再利用(同じ見た目が2回以上出てくる)」「見通し(名前をつけると読みやすくなる)」です。
- 責務: 「このボタンは何をする部品?」に一言で答えられる大きさに割る。答えに『と』が多いなら分けどき
- 再利用: 同じUIが2回以上登場したら、共通の部品にまとめて使い回す
- 見通し: 中身に <UserCard /> のような名前がつくと、親のreturnが読みやすくなる
なぜ「1部品1責務」を目安にするのか
1つの部品が複数の仕事を抱えると、片方を直したいだけなのに全体を読む羽目になり、変更が別の機能を壊しやすくなります。責務を1つに絞ると、直す範囲が狭くなり、テストも説明も楽になります。名前を一言でつけられるかが、ちょうどよい大きさの目安です。
// 何でも屋。読むのも直すのも大変
function UserList() {
const users = [{ id: 1, name: "田中" }, { id: 2, name: "佐藤" }];
return (
<ul>
{users.map((u) => (
<li key={u.id}>
<span>{u.name}</span>
<button>フォロー</button>
</li>
))}
</ul>
);
}// 親は「並べる」責務だけ
function UserList({ users }: { users: { id: number; name: string }[] }) {
return (
<ul>
{users.map((u) => (
<UserRow key={u.id} name={u.name} />
))}
</ul>
);
}
// 子は「1人分の見た目」責務だけ。名前で意図が伝わる
function UserRow({ name }: { name: string }) {
return (
<li>
<span>{name}</span>
<button>フォロー</button>
</li>
);
}propsのバケツリレー(prop drilling)問題
部品を細かく分けると、便利な反面あたらしい面倒が生まれます。深いところにある子が値を必要とするとき、その値を親から子へ、子から孫へ……と、途中の部品が使いもしないのに手渡しし続けることになります。これがpropsのバケツリレー(prop drilling)です。途中の部品が「自分は使わないのに受け取って渡すだけ」の余計な props を持つのが特徴です。
// user は Page → Layout → Header → Avatar と延々と手渡しされる
function Page({ user }: { user: User }) {
return <Layout user={user} />;
}
function Layout({ user }: { user: User }) {
return <Header user={user} />; // Layout自身はuserを使わない
}
function Header({ user }: { user: User }) {
return <Avatar user={user} />; // Headerも使わない、渡すだけ
}
function Avatar({ user }: { user: User }) {
return <img alt={user.name} src={user.icon} />; // ここでやっと使う
}浅いバケツリレーなら、むしろ素直で読みやすいので気にしなくてかまいません。問題になるのは、多くの階層を「使わない値」が延々と通り抜けるときです。そういう「アプリ全体で広く使う値(ログイン中のユーザー、テーマの色、言語設定など)」に対して、途中を飛ばして直接届ける仕組みが Context(コンテキスト)です。
Contextは「広く共有する値」の近道(深入りしない)
Contextは、途中の部品を経由せずに、離れた子へ直接値を届ける仕組みです。イメージは『館内放送』。各部屋を伝言で回さなくても、放送すれば必要な部屋が直接受け取れます。今の段階では『バケツリレーが深くつらくなったら、広く使う値にはContextという道具がある』と読めれば十分です。細かい書き方は必要になってから学べば間に合います。
深いバケツリレー
中間の部品が使わないpropsを何段も受け渡す。追加・変更がつらい
<Page user={user} />
// → Layout(user) → Header(user) → Avatar(user)
// 途中は使わないのに全員 user を持つ広い値はContext
広く使う値は途中を飛ばして直接届く。中間の部品はpropsが減る
// おおまかなイメージ
// <UserProvider value={user}> で包み
// Avatar 側で useUser() でもらう
// → 中間の Layout / Header は user を持たないAIにはこう聞く
「このコンポーネント、userを4階層も手渡ししていてつらい。バケツリレーを解消したい。Contextを使うべきか、それとも設計を見直すべきか教えて」と現状を貼って相談します。判断の基準は『その値は本当にアプリ全体で広く使う値か』。狭い範囲ならpropsのままでよい、と自分で線引きできると、AIの提案を鵜呑みにせず選べます。
分割は責務・再利用・見通しの3つで判断し、割りすぎない。propsのバケツリレーが深くつらくなったら、広く使う値にはContextという近道がある。まずはこの地図が頭に入っていれば十分です。
読んでも腑に落ちない所は、AIに噛み砕いてもらいましょう。
理解度チェック
答えられれば、面接で説明できる状態に一歩近づきます。
コンポーネントを分割するかどうかの判断基準として、本文で挙げた3つの物差しはどれ?
propsのバケツリレー(prop drilling)とは何を指す?
バケツリレーが深くてつらいとき、Contextを使うかどうかは何を基準に判断しますか。
記述式です。まず自分の言葉で答えを考えてから、模範解答を開いて自己採点してください。