今回は 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
を正しく使いこなしましょう 🙌