Day 5・状態の設計と再利用 — 大きくしても壊れない作り
カスタムHook — ロジックをまとめて再利用する
use〜で始まる自作関数にロジックをまとめて再利用し、ロジックとUIを分ける発想を、読めて説明できる形でつかむ。
同じ手順が何度も出てきたら、名前をつけてまとめる
画面をいくつも作っていると、同じ状態管理の手順が繰り返し登場します。たとえば「数を数える(useStateして、増やす・減らす関数を用意する)」や「データを取ってくる(読み込み中フラグと結果を管理する)」。これらの手順を毎回コピペすると、直すときに全部を直す羽目になります。そこで、手順に名前をつけて1か所にまとめるのがカスタムHook(自作フック)です。
カスタムHookの正体は、ただの関数です。ふつうの関数と違う点は2つだけ。名前が use で始まること、そして中で useState などのReactの仕組みを使えることです。use で始まる名前にするのは、Reactに「これはHookです」と伝える約束事です。難しく考えず、『useStateなどをまとめて包んだ、use始まりの関数』と読めれば十分です。
// use で始まる関数。カウントのロジックだけを担当する
function useCounter(initial: number) {
const [count, setCount] = useState(initial);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initial);
// UIは返さない。値と操作関数だけを返す
return { count, increment, decrement, reset };
}// コンポーネントは「見た目」に集中
function CounterButton() {
const { count, increment } = useCounter(0);
return <button onClick={increment}>{count} 回</button>;
}
// 別の画面でも同じロジックを再利用できる
function StepCounter() {
const { count, increment, reset } = useCounter(10);
return (
<div>
<p>{count}</p>
<button onClick={increment}>+1</button>
<button onClick={reset}>リセット</button>
</div>
);
}なぜロジックとUIを分けるのか
コンポーネントに「状態の管理」と「見た目」が混ざっていると、片方を直したいだけなのに両方を読む必要があり、他の画面で同じ処理を使い回せません。ロジックをカスタムHookへ切り出すと、コンポーネントは見た目に集中でき、ロジックは1か所で直せて、どの画面からも呼べます。分けることで『読める・直せる・再利用できる』が同時に手に入ります。
もう1つ代表例を見ましょう。データをAPIから取ってくる処理は、どの画面でも「読み込み中」「成功」「失敗」を扱う似た手順になります。これを useFetch のようなカスタムHookにまとめると、各画面は結果を受け取って表示するだけで済みます。ここでは中身を丸暗記する必要はありません。『こういう形のHookがある』と読めれば十分です。
// 読み込み中・データ・エラーの3つの状態をまとめて管理
function useFetch(url: string) {
const [data, setData] = useState<unknown>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((json) => setData(json))
.finally(() => setLoading(false));
}, [url]);
return { data, loading };
}
// 使う側は表示に集中できる
function UserName({ url }: { url: string }) {
const { data, loading } = useFetch(url);
if (loading) return <p>読み込み中...</p>;
return <p>取得完了</p>;
}- カスタムHookはただの関数。名前を use で始めるのが約束事
- 中で useState や useEffect などのHookを使える(ここが普通の関数と違う点)
- UI(JSX)は返さず、値と操作関数を返す。見た目は使う側のコンポーネントが担当
- 同じロジックを複数の画面で使い回せ、直すときは1か所だけ
ロジックとUIが混在
各コンポーネントが状態管理をそれぞれ書き、コピペで散らばる
function A() {
const [count, setCount] = useState(0);
// 増減ロジックをここに直書き…
}
function B() {
const [count, setCount] = useState(0);
// また同じロジックを直書き(コピペ)
}ロジックを分離
ロジックはuseCounterに集約。各コンポーネントは呼ぶだけ
function A() {
const { count, increment } = useCounter(0);
}
function B() {
const { count, increment } = useCounter(0);
}
// 直すときは useCounter 1か所AIにはこう聞く
「このコンポーネントの中の状態管理ロジックを、useなんとかというカスタムHookに切り出して。UIは元のコンポーネントに残して」と頼めば、分離を手伝ってくれます。確認するのは2点だけ。名前が use で始まっているか、そしてHookが『値と操作関数を返し、JSXは返していないか』。この形になっていれば、ロジックとUIがきちんと分かれています。
カスタムHookは『use始まりの、ロジックをまとめた関数』。UIは返さず、値と操作を返す。これで見た目とロジックが分かれ、再利用が効きます。今日の段階では、自分でゼロから書けなくても、読んで意図を説明できれば十分です。
読んでも腑に落ちない所は、AIに噛み砕いてもらいましょう。
理解度チェック
答えられれば、面接で説明できる状態に一歩近づきます。
カスタムHookの説明として正しいものはどれ?
ロジックをカスタムHookに切り出す主な利点はどれ?
カスタムHookで「ロジックとUIを分離する」とはどういうことか、useCounterを例に説明してください。
記述式です。まず自分の言葉で答えを考えてから、模範解答を開いて自己採点してください。