Tyotto good!

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

Posted: March 07, 2021

今回は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関数は変化することがないように見え、ログは初回レンダリング時しか出力されないように思えます。しかし、こちら実行すると、以下のようにログが出力されます。

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

useCallbackを使った例

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

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

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

これは冒頭で説明した通り、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 を正しく使いこなしましょう🙌