R初めてのReact

Day 5状態の設計と再利用 — 大きくしても壊れない作り

カスタムHook — ロジックをまとめて再利用する

use〜で始まる自作関数にロジックをまとめて再利用し、ロジックとUIを分ける発想を、読めて説明できる形でつかむ。

同じ手順が何度も出てきたら、名前をつけてまとめる

画面をいくつも作っていると、同じ状態管理の手順が繰り返し登場します。たとえば「数を数える(useStateして、増やす・減らす関数を用意する)」や「データを取ってくる(読み込み中フラグと結果を管理する)」。これらの手順を毎回コピペすると、直すときに全部を直す羽目になります。そこで、手順に名前をつけて1か所にまとめるのがカスタムHook(自作フック)です。

カスタムHookの正体は、ただの関数です。ふつうの関数と違う点は2つだけ。名前が use で始まること、そして中で useState などのReactの仕組みを使えることです。use で始まる名前にするのは、Reactに「これはHookです」と伝える約束事です。難しく考えず、『useStateなどをまとめて包んだ、use始まりの関数』と読めれば十分です。

TSX
// 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 };
}
カウンターのロジックをまとめた useCounter。中身はいつものuseState
TSX
// コンポーネントは「見た目」に集中
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だけに集中でき、同じロジックをどの画面でも呼べる

なぜロジックとUIを分けるのか

コンポーネントに「状態の管理」と「見た目」が混ざっていると、片方を直したいだけなのに両方を読む必要があり、他の画面で同じ処理を使い回せません。ロジックをカスタムHookへ切り出すと、コンポーネントは見た目に集中でき、ロジックは1か所で直せて、どの画面からも呼べます。分けることで『読める・直せる・再利用できる』が同時に手に入ります。

もう1つ代表例を見ましょう。データをAPIから取ってくる処理は、どの画面でも「読み込み中」「成功」「失敗」を扱う似た手順になります。これを useFetch のようなカスタムHookにまとめると、各画面は結果を受け取って表示するだけで済みます。ここでは中身を丸暗記する必要はありません。『こういう形のHookがある』と読めれば十分です。

TSX
// 読み込み中・データ・エラーの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>;
}
データ取得のロジックを包んだ useFetch(読めればよい)
  • カスタムHookはただの関数。名前を use で始めるのが約束事
  • 中で useState や useEffect などのHookを使える(ここが普通の関数と違う点)
  • UI(JSX)は返さず、値と操作関数を返す。見た目は使う側のコンポーネントが担当
  • 同じロジックを複数の画面で使い回せ、直すときは1か所だけ

ロジックとUIが混在

各コンポーネントが状態管理をそれぞれ書き、コピペで散らばる

TSX
function A() {
  const [count, setCount] = useState(0);
  // 増減ロジックをここに直書き…
}
function B() {
  const [count, setCount] = useState(0);
  // また同じロジックを直書き(コピペ)
}

ロジックを分離

ロジックはuseCounterに集約。各コンポーネントは呼ぶだけ

TSX
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に噛み砕いてもらいましょう。

理解度チェック

答えられれば、面接で説明できる状態に一歩近づきます。

Q1

カスタムHookの説明として正しいものはどれ?

Q2

ロジックをカスタムHookに切り出す主な利点はどれ?

Q3

カスタムHookで「ロジックとUIを分離する」とはどういうことか、useCounterを例に説明してください。

記述式です。まず自分の言葉で答えを考えてから、模範解答を開いて自己採点してください。