【React】 カスタムフックでロジックを外出し

React Hooksの特徴は、それぞれのhookをコンポーネントの外で記述できる点です。これにより、複雑なロジックをコンポーネント外に押し出し、再利用できる状態にすることで、シンプルなコンポーネントを構築できます。

Hookとは

そもそもReactのhookは、クラスを使わずにReactの機能を書ける機能です。Reactの機能とは、statecontexteffect等です。

export default function App() {
    // useStateはhookのひとつ
    const [count, setCount] = useState(0);
    
    // useCallbackはhookのひとつ
    const incrementCount = useCallback(() => {
        setCount(count + 1);
    }, [count]);

    // useEffectもhookのひとつ
    useEffect(() => {
        // ...
    }, [count]);

    return (
        <>
        <p>{ count }</p>
        </>
    );
}

一瞬ホックと読みたくなりますが、フックです。

カスタムフックでHookをコンポーネントの外に出す

Hookの良いところは、まとまったロジックを外にだすことができる点です。このようにして作った新しいHookをカスタムフックと呼びます。

カスタムフックというと難しく聞こえますが、要はHookを使ったロジックをコンポーネントの外に出しているだけです。

例えば、ボタンを押すと数値が増減する仕組みを考えてみます。普通に作ると、例えば

普通に作った場合
export default function App() {

    // ここから

    const [count, setCount] = useState(5);

    const increment = () => {
        setCount(count + 1);
    };

    const decrement = () => {
        setCount(count - 1);
    }

    // ここまでをカスタムフックとして切り出す

    return (
        <>
        <p>{ count }</p>
        <button onClick={increment}>+1</button>
        <button onClick={decrement}>-1</button>
        </>
    );
}

のようなものができます。このcountに関するロジックをカスタムフックとして切り出してみると、例えば

features/counter.js
import { useState } from "react"

// カスタムフックのコード
export const useCounter = (init) => {
    const [count, setCount] = useState(init);

    const increment = () => {
        setCount(count + 1);
    };

    const decrement = () => {
        setCount(count - 1);
    }

    return [count, {increment, decrement}];
};
app.js
export default function App() {
    // カスタムフックの利用
    const [count, {increment, decrement}] = useCounter(5);

    return (
        <>
        <p>{ count }</p>
        <button onClick={increment}>+1</button>
        <button onClick={decrement}>-1</button>
        </>
    );
}

のように切り出すことができます。これにより、複数箇所に同じようなロジックを書く必要性がなくなります。

Reactのページには、

カスタムフックとは、名前が ”use” で始まり、ほかのフックを呼び出せる JavaScript の関数のことです。
https://ja.reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook

と書かれています。そのため、カスタムフック名は上の例のようにuseCounterなど、useをつけるべきです。

また、Hookが使える場所は、関数コンポーネント内か、カスタムフック内です。

関数のメモ化

useCallbackなどを用いたメモ化はReactのパフォーマンスを上げる上で必須です。

ここで、カスタムフックの呼び出される際に、関数などが再度作成されないように、ここでもuseCallbackなどを用いていきましょう。

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

    const increment = useCallback(() => {
        setCount(count + 1);
    }, [count]);

    const decrement = useCallback(() => {
        setCount(count - 1);
    }, [count]);

    return [count, {increment, decrement}];
};

また、カスタムフック全体を毎回実行するのはコストがかかるので、useMemoを用いて無駄な呼び出しを削減できる場合もあるなら、それも活用していくと良いです。

カスタムフックの戻り値

カスタムフックを作成する際は、何を返すべきか悩みます。基本的には、

  • 特定のuseXxの形式に合わせる
  • 全部返す

のどちらかが基本になります。

各種Hookの戻り値の形式

特定のHookに戻り値に合わせるため、それぞれの戻り値を知っておきましょう。

Hook戻り値
useState{stateの値, {stateの更新関数}}
useReducer{stateの値, {stateの更新関数}}
useEffectなし
useCallbackそれそのもの (1個)
useRefそれそのもの (1個)

ここで、useStateでは{state, setState}のような形式でした。カスタムフックでは更新用関数が複数あることが普通です。これは、どのような更新を行うかをカプセル化するためです。

普通のuseState
const [count, setCount] = useState(0);
カスタムフックでのuseState風の戻り値
export const useCounter = (init) => {
    const [count, setCount] = useState(init);

    const increment = useCallback(() => {
        setCount(count + 1);
    }, [count]);

    const decrement = useCallback(() => {
        setCount(count - 1);
    }, [count]);

    // 複数個の更新関数を返す
    return [count, {increment, decrement}];
};
カスタムフックの呼び出し側
const [count, {increment, decrement}] = useCounter(5);

カスタムフックの利点

上の実際の例を見た上で、どのような利点があるのでしょうか。

  • コンポーネントの複雑化を防ぐ
  • 機能を再利用できる
  • テストが容易になる

2つ目と3つ目は関連していますね。機能を再利用できるということはそれ単体で動かせる状態ということであり、単体で動かせるということはテストが簡単になるということです。

また、ロジックの一部をコンポーネントの外に出すので、コンポーネントの複雑化を防ぐことができます。

まとめ

カスタムフックを用いることで、コンポーネントの肥大化や複雑化を防ぐことができます。また、ロジックの使い回しもしやすくなります。

大規模な開発となると必ず使用するため、小規模な個人制作のものでもどんどん使って練習していくべきです。

react custom hook thumb

役に立ったらシェアしよう!