【React入門】カスタムフックの作り方と実践例|ロジックを再利用してコードを整理する方法

Reactカスタムフックの作り方と実践例

はじめに|なぜReactカスタムフックを学ぶのか

Reactのカスタムフック

正直に言うと、僕はこれまで React のカスタムフックを実務で使ったことはありません
これまでは useStateuseEffect を組み合わせて必要な処理を書き、なんとか回してきました。

ただ、しっかり調べてみると「カスタムフックに切り出すだけで、ロジックの再利用性と見通しが一気に良くなる」ことがわかりました。

フォーム入力、API 通信、イベントリスナーの登録・解除など、どのコンポーネントでも繰り返し出てくる“同じような処理”小さな部品(関数)として共有できるのがカスタムフックです。

この記事では、僕が調べて理解した内容をベースに、作り方と実践例

「コード全文 → 抜粋 → ていねいな解説」

の順でまとめます。はじめての方でもスッと読めるように、言葉はできるだけやさしくしています!


Reactにおけるカスタムフックとは?

  • 定義use で始まる通常の関数。中で useState / useEffect / useRef など 既存のフックを組み合わせて作る。
  • 役割:コンポーネントから ロジック(状態+副作用)を切り出して再利用できるようにする「箱」。
  • 効果
    • 1つの責務にまとめられる(フォーム値の管理、データ取得、イベント購読など)
    • UI(JSX)とロジックが分離されて コンポーネントが読みやすくなる
そたや

例えるなら「UIは見た目、カスタムフックは頭脳」。見た目と頭脳を分ければ、後から直すのもラクになります。


Reactでカスタムフックを作る基本ルール

Reactでカスタムフックを作る基本ルール
  • 関数名は use ではじめる(例:useCounter, useInput
  • フックのルールを守る(トップレベルだけで呼ぶ/条件分岐やループの中で呼ばない)
  • 入出力をシンプルに(返すデータや関数の形は、使う側が迷わない API に)

Reactカスタムフックの最小サンプル:useCounter

コード全文(useCounter.ts

import { useState, useCallback } from "react";

export function useCounter(initial = 0, step = 1) {
  const [count, setCount] = useState(initial);

  const increment = useCallback(() => setCount((c) => c + step), [step]);
  const decrement = useCallback(() => setCount((c) => c - step), [step]);
  const reset = useCallback(() => setCount(initial), [initial]);

  return { count, increment, decrement, reset };
}

使い方(呼び出し側 Counter.tsx

import { useCounter } from "./useCounter";

export default function Counter() {
  const { count, increment, decrement, reset } = useCounter(0, 1);

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
      <button onClick={reset}>リセット</button>
    </div>
  );
}

抜粋&詳解

const [count, setCount] = useState(initial);
  • フック内に状態を持つ。呼び出し側は 状態の中身だけ使える
const increment = useCallback(() => setCount((c) => c + step), [step]);
  • useCallback関数の参照を安定化。呼び出し側の再レンダリングを無駄に増やさない。
  • 依存配列に step を入れることで、step が変わったときに関数を更新。
return { count, increment, decrement, reset };
  • 返す API は最小限でわかりやすく。オブジェクトで返すと、使う側で名前付きで取り出せて読みやすい。

Reactでフォーム入力をまとめる:useInput

コード全文(useInput.ts

import { useState, useCallback } from "react";

export function useInput(initial = "") {
  const [value, setValue] = useState(initial);

  const onChange = useCallback(
    (e) => setValue(e.target.value),
    []
  );

  const reset = useCallback(() => setValue(initial), [initial]);

  return { value, onChange, reset, setValue };
}

使い方(ProfileForm.tsx

import { useInput } from "./useInput";

export default function ProfileForm() {
  const name = useInput("");
  const email = useInput("");

  return (
    <form>
      <div>
        <label>名前</label>
        <input type="text" value={name.value} onChange={name.onChange} />
      </div>
      <div>
        <label>メール</label>
        <input type="email" value={email.value} onChange={email.onChange} />
      </div>

      <button type="button" onClick={() => { name.reset(); email.reset(); }}>
        リセット
      </button>
    </form>
  );
}

抜粋&詳解

const onChange = useCallback((e) => setValue(e.target.value), []);
  • 入力欄の定番パターンを 1行に集約。呼び出し側は valueonChange をセットで渡すだけ。
return { value, onChange, reset, setValue };
  • 直接値を変えたい場面のために setValue も公開。使い勝手の良い API に。

Reactでウィンドウサイズを取得する:useWindowSize

コード全文(useWindowSize.ts

import { useState, useEffect } from "react";

export function useWindowSize() {
  const isClient = typeof window !== "undefined";

  const get = () => ({
    width: isClient ? window.innerWidth : 0,
    height: isClient ? window.innerHeight : 0,
  });

  const [size, setSize] = useState(get);

  useEffect(() => {
    if (!isClient) return;

    const onResize = () => setSize(get());
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [isClient]);

  return size; // { width, height }
}

使い方(Layout.tsx

import { useWindowSize } from "./useWindowSize";

export default function Layout() {
  const { width } = useWindowSize();
  const isMobile = width < 768;

  return <div>{isMobile ? "モバイル表示" : "デスクトップ表示"}</div>;
}

抜粋&詳解

const isClient = typeof window !== "undefined";
  • SSR(サーバーサイドレンダリング)環境では window が無いので 安全に分岐
useEffect(() => {
  if (!isClient) return;
  const onResize = () => setSize(get());
  window.addEventListener("resize", onResize);
  return () => window.removeEventListener("resize", onResize);
}, [isClient]);
  • 登録したイベントはクリーンアップで必ず解除。メモリリークや重複実行を防ぐ。


Reactでカスタムフックを使うメリットと注意点

メリット

  • 再利用性:複数コンポーネントで同じロジックを共有
  • 見通し:JSX がスッキリし、UI とロジックが分離
  • テスト容易性:ロジック単位で検証しやすい

注意点

  • 過剰分割しない:そのコンポーネントでしか使わない処理まで分けると逆に読みにくい
  • API を最小に:返しすぎると迷う。使う側の目線で設計
  • 依存配列を正しく書くuseEffect / useCallback の依存漏れはバグの温床
  • 副作用の掃除を忘れない:イベントやタイマーはクリーンアップで解除

Reactカスタムフックでよくあるつまずき

  1. フックのルール違反
    • ループや条件分岐の中で呼ばない。トップレベルだけで呼ぶ。
  2. 依存配列の書き忘れ/過不足
    • 依存を入れ忘れると古い値(stale)を参照。入れすぎると無限ループ。
  3. イベント購読の解除漏れ
    • useEffectreturn で必ず解除してリソースを解放。
  4. 不安定な関数参照
    • 呼び出し側の再レンダリングを増やさないために useCallbackで安定化。
  5. “なんでも屋” フック
    • 1 フック 1 目的が基本。機能を詰め込みすぎない。

まとめ|Reactカスタムフックでロジックを整理しよう

Reactでカスタムフックを使うメリットと注意点
  • カスタムフックは 「ロジックの箱」。UI と分けて 再利用見通しアップ。
  • まずは useCounteruseInput のような 小さなフックから始め、
    慣れてきたら useWindowSize のような 副作用系に挑戦。
  • フックのルール・依存配列・クリーンアップの3点を守れば、実務でも安心して使えます。

僕自身、実装経験はこれからですが、調べて分かったのは「カスタムフックがあるだけで設計の幅が広がる」ということ。

“頭脳はフックに、見た目はコンポーネントに” を意識して、少しずつ取り入れていきます!


関連記事|React入門シリーズでさらに学ぶ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です