【React Hooks】useCallBackの使い所を理解しよう

Posted: 2021/3/8
React/
React Hooks

今回は React Hooks のuseCallBackについて説明します 🙌

useCallBack とは

useCallBackは、メモ化されたコールバック関数を返します。メモ化とは同じ入力が再度発生した時に、キャッシュした結果を返すことです。

メモ化については、こちらの記事でも説明しています。

useCallBackは以下のように定義します

const memorizedCallback = useCallback(() => {
  callbackFunc(value);
}, [value]);

useCallbackの第一引数にはuseEffectと同様にコールバック関数を受け取り、第二引数も同様に依存配列を受けます。依存配列にセットされた値のいずれかが変化した場合のみ useCallback の戻り値が再計算されます。

では次にuseCallback の使い所について説明します。

関数の同一性を理解する

まず useCallback の使い所を理解するために、関数の同一性について説明します。関数はオブジェクト型であるので同じメモリに保存されているかどうかで同一かを判断します。

以下のような例を考えてみます。

function hoge() {
  console.log("hoge");
}

const hoge1 = hoge();
const hoge2 = hoge();

// false
hoge1 === hoge2;

hoge1、hoge2 は同じ関数を受け取っているのですが、参照先が異なる(メモリアドレスが異なる)ため同一とは見なされません。

これを踏まえて、useCallback の使い所を考えてみましょう。

useCallback の使い所

useEffect の依存配列に関数を用いる場合

React はレンダリング毎にコンポーネント内の関数は別の関数に変わるため、useEffectの依存配列に関数を用いると、レンダリング毎に useEffect に渡したコールバック関数が呼ばれてしまうことになります。

以下のような例を考えてみましょう。input 要素と button が並び outputLog 関数が変更されるたびに outputLog 関数が呼び出されるようになっています。

import React, { useEffect, useState } from "react";

const UseCallbackSample: React.FC = () => {
  const [message, setMessage] = useState("");
  const [count, setCount] = useState(0);
  // レンダリングごとに別の関数になる
  const outputLog = (value: string) => {
    console.log(value);
  };
  // 別の関数になるので、レンダリングごとにログが出力されてしまう
  useEffect(() => {
    outputLog(message);
  }, [outputLog]);

  return (
    <>
      <input
        type="text"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button onClick={() => setCount(count + 1)}>click me</button>
    </>
  );
};

export default UseCallbackSample;

一見、上の例では outputLog 関数は変化することがないように見え、ログは初回レンダリング時しか出力されないように思えます。しかし、こちら実行すると、以下のようにログが出力されます。

useCallBack1

React がレンダリングごとに異なる関数をもっており、その関数をuseEffectの依存配列に入れているのでレンダリングごとにログが出力されてしまっています。

この問題は、useCallback を使うと解決することができます。

useCallback を使った例

例えば、先ほどの例で state である message が変更した時のみ、コールバック関数を変更するようにしてみます。outputLog 関数は以下のようになります。

const outputLog = useCallback(
  (value: string) => {
    console.log(value);
  },
  [message]
);

こちらに修正したのち実行すると、ボタンがクリックされ、state である count が更新されてもログは出力されなくなります。

useCallBack2

これは冒頭で説明した通り、useCallbackを使うことで依存配列に設定した message の値が変わる時のみ関数を変更するようにしているためです。

useEffectの依存配列に関数を用いる場合は、レンダリングごとに関数が変更することを意識して、useCallbackを使って関数の変更を調整するとよいでしょう。

React.memo と組み合わせて使う

React.memo はコンポーネントをメモ化する高階コンポーネントで、以下のように利用します。

const Message = React.memo(props => <div>message: {props.message}</div>);

新しく渡された props と前の props を比較して、等価であれば再レンダリングを行わずにメモ化したコンポーネントを再利用します。

なので、この props に関数を入れる場合、useCallbackでメモ化した関数を使うとメモ化されたコンポーネントを再利用でき、最適化しやすくなります。

まとめ

  • useCallBackは、メモ化されたコールバック関数を返す
  • useEffectの依存配列に関数を用いる場合はuseCallbackを使う手がある
  • React.memoと組み合わせて使うことでパフォーマンス最適化がしやすくなる

useCallback を正しく使いこなしましょう 🙌