Skip to the content.

カスタムフック

目次

カスタムフックとは

カスタムフックとは、独自で実装するフックのことです。フックとは、コンポーネントで使うロジックを再利用可能な関数にしたものです。これまでの章で出てきたuseStateuseEffectuseRefなどがそれに当たります。

カスタムフックができることを簡単に説明すると、以下のCounterコンポーネントのロジック部分を関数useCounterとしてまとめることができます。

import { useLayoutEffect, useState } from "react";

const Counter: React.FC = () => {
  const [count, setCount] = useState(0);

  useLayoutEffect(() => {
    if (count >= 10) setCount(0);
  }, [count]);

  return (
    <div>
      <div className="text-lg">{count}</div>
      <button
        className="rounded-lg bg-gray-300 px-2"
        onClick={() => {
          setCount((prev) => prev + 1);
        }}
      >
        +
      </button>
    </div>
  );
};

export default Counter;

上記コードでカスタムフックuseCounterを自分で定義すると、以下のようにできます。

import { useCounter } from "../hooks/useCounter";

const Counter: React.FC = () => {
  const { count, add } = useCounter();

  return (
    <div>
      <div className="text-lg">{count}</div>
      <button
        className="rounded-lg bg-gray-300 px-2"
        onClick={() => {
          add();
        }}
      >
        +
      </button>
    </div>
  );
};

export default Counter;

カスタムフックの利点

ロジックの分離

カスタムフックを利用すれば、コンポーネントはUI、カスタムフックはロジックというように分離することができます。こうすることでテストが容易になるという利点があります。

ロジックの使い回し

大規模な開発になってくると、同じようなロジックを様々なコンポーネントで使う場合が出てきます。そのような場合に、カスタムフックを定義すれば、コンポーネントごとにロジックを定義する必要はなく、カスタムフックを利用するだけで良くなります。

もし、その処理でバグがあった場合、カスタムフックを利用していた場合いれば、該当するカスタムフックのみを修正するだけでよくなります。

利点まとめ

以上のことから、カスタムフックは、以下の場合に使うのが良いでしょう。

プロジェクトによっては、ロジックは全てカスタムフックに記述し、ロジックを完全に分離させる場合もあるようです。どこまでカスタムフックを使うかは、プロジェクトごとに決めるのが良いですが、最低限でも上記の場合は、利用するのがよさそうです。

カスタムフックの実装

それでは、実際に上で紹介したuseCounterを実装していきましょう。

準備

まずは、カスタムフックの実装する前のコンポーネントを実装しましょう。以下のコンポーネントCounterWithCustomHookを作成してください。

src/components/CounterWithCustomHook.tsx

import { useLayoutEffect, useState } from "react";

const CounterWithCustomHook: React.FC = () => {
  const [count, setCount] = useState(0);

  useLayoutEffect(() => {
    if (count >= 10) setCount(0);
  }, [count]);

  return (
    <div>
      <div className="text-lg">{count}</div>
      <button
        className="rounded-lg bg-gray-300 px-2"
        onClick={() => {
          setCount((prev) => prev + 1);
        }}
      >
        +
      </button>
    </div>
  );
};

export default CounterWithCustomHook;

このコンポーネントの実行結果が分かるように、Appコンポーネントを以下のように修正してください。

src/App.tsx

import CounterWithCustomHook from "./components/CounterWithCustomHook";

function App() {
  return (
    <div className="m-4 space-y-2">
      <CounterWithCustomHook />
    </div>
  );
}

export default App;

npm run devを実行して結果をブラウザで確認してください。

これは、6章で作成した上限が9のカウンターと同じものです。表示されている数字が9の時に、+ボタンを押すと数字が0に戻ります。

このコンポーネントをカスタムフックを用いてリファクタリングを行っていきます。

useCounterの実装

srcディレクトリ内にhooksディレクトリを作成し、このなかにカスタムフックのソースコードを配置していきます。

カスタムフックは、慣習的に以下のようなルールがあります。

それでは、useCounterを実装していきます。以下のファイルを作成してください。このカスタムフックは、jsxを使わないので、拡張子は.tsで良いです。

src/hooks/useCounter.ts

export const useCounter = () => {
  // ここにロジックを記述
};

このuseCounterCounterWithCustomHookコンポーネントのロジックを書いていきます。

どこまでカスタムフックに入れるのかは、特にルールはありませんが、ここではReactのhooksを利用している箇所をカスタムフックに入れていきます。

CounterWithCustomHookコンポーネントでReactのhooksを使っているのは、以下の箇所です。

これをカスタムフックに含めると、useCounterは以下のようになります。

import { useLayoutEffect, useState } from "react";

export const useCounter = () => {
  const [count, setCount] = useState(0);

  useLayoutEffect(() => {
    if (count >= 10) setCount(0);
  }, [count]);

  const add = () => {
    setCount((prev) => prev + 1);
  };

  return { count, add };
};

次に、このカスタムフックを使ってCounterWithCustomHookコンポーネントをリファクタリングしましょう。

CounterWithCustomHookコンポーネントは、以下のように変更してください。

src/components/CounterWithCustomHook.tsx

import { useCounter } from "../hooks/useCounter";

const CounterWithCustomHook: React.FC = () => {
  const { count, add } = useCounter();

  return (
    <div>
      <div className="text-lg">{count}</div>
      <button
        className="rounded-lg bg-gray-300 px-2"
        onClick={() => {
          add();
        }}
      >
        +
      </button>
    </div>
  );
};

export default CounterWithCustomHook;

npm run devを実行して結果をブラウザで確認してください。 リファクタリング前と動作が変わらなければ、成功です。

Next: Chapter9 グローバルな状態管理 zustand

Prev: Chapter7 DOM操作 useRef, createPortal