🧊

useCallbackちゃんは必要なのか? 私のサマータイムレコード②

2022/08/15に公開約2,700字

僕のサマータイムレコード  ② useCallback

夏の記録として日々業務などで勉強したことを書かんとするプロジェクトです。
今回は useCallback について書いていきたいと思います。

フワッと使っている、useCallback

自分は useCallback を結構業務でふわっと使ってしまっています。が、そもそもどんなタイミングで、何を目的とするべきかはわかっていません。
「フワッとしてるだけだとまずい、、、!」そんな思いから、今回は useCallback をしっかりと学習してみました。

まずは useMemo から学ぶ

まずは useCallback を語る上で関連してくる、 useMemo から勉強しましょう。
useMemo は値のメモ化を行うフックです。簡単にいうと、計算結果を保持して、再利用を行います。

const test = useMemo(
  () => {
    // factory function
  },
  [
    // 依存する値
  ]
);

useMemo の目標は、不要なレンダリングコストを減らすことです。
依存する値が変更されない限り、キャッシュした計算結果を返し続けます。
react では際レンダリングが発生した場合にそのコンポーネント内の関数の再生成、処理の再実行が行われますがそれを防ぐことができるわけです。
また、このフックは React.memo と併用することでさらに強みをますことができます。
React.memo は props の値の変化によって再レンダリングを行うかを判断しますが、useMemo で同一のオブジェクトを使い回すことでレンダリング回数を減らせるわけです。
(たとえばオブジェクトの値が変わらなくても、メモ化していないとオブジェクトの参照が変わってしまうため、別オブジェクトとして判定して React.memo は不要なレンダリングを起こします。)

useCallback はじゃあ何なの?

本題の useCallback に入りましょう。
useCallbackuseMemo とは違い、関数をメモ化します。
大きな違いはそこで、結果の値なのか、関数なのかが大きな違いになります。

const exampleFunction = useCallback(
  () => {
    // callBackFunction
  },
  [
    // 依存する値
  ]
);

useCallback の目標も不要なレンダリングコストをかけないことにあります。
先ほどの useMemo と同じように、 React.memo でメモ化された場合のコンポーネントが関数オブジェクトを受け取る場合や、また、 useEffect などほかのフックの依存として関数が存在する場合、有効な手段になリます。
useCallback はあくまで依存が変わった時に、 関数を再生成する ということを理解しましょう。

なんとなく useCallback は理解したけど、、、

さて、ここまででなんとなく useCallback については理解いただけたかと思います。
が、個人的に少しわからないことがあるので、つらつらと書いていきます。

useCallback を考えなしに使うことに意味はあるのか

はい、絶対に当たる壁になるところだと思います。
よくとりあえず useCallback で書いておけ、みたいな記事をネットで頻繁に見るかと思います。
果たしてこれって正しいんだろうか?という疑問です。
個人的に思う疑問点をあげると

  1. 不要な useCallback はいろんなコストを上げるだけになりそうじゃないか?
  2. そもそも useCallback をしないとならないほどのコストのかかりまくる実装って何?

というところです。

1 つ目はシンプルで、あんまり計画のない useCallback はただコストを膨らませるだけになりそうという話です。
前提として、 useCallback は依存する値も同様にメモ化されていることを前提にしているかと思っています。
となると、途中でメモ化したくなった時に、依存関係も書き換えて、色々やって、、、となると、コストが急に膨れそうだなと思ってしまします。
また、単純に書き換えるとかであればいいのですが、そこからさらに計測までしたいとなると、結構な地獄を見そう、、、とも思ってしまいます。
2 つ目もシンプルで、 useCallback で生成を抑制しないとならないほどの関数とは何?という疑問です。
FE ではそれほどの超巨大になる関数なぞない、というのが個人的な意見で、そんなに大きな関数になるなら、そもそもどこかしら(BE との連携の仕方、そもそもの使用などなど、、、)がおかしいのではないか、と思ってしまいます。
なので、そもそも useCallback で抑えるほどのものがどれだけあるのだろうか、というのが個人的には見えないなと思います。

まとめ 考えても無駄で、測るしかない話

結局のところ、私は useCallback については理解できましたが、それがどれだけの恩恵をもたらすか、また、いつ使うべきかは分かりませんでした。
useCallback などの最適化の hook については、結論計測するしかないと思っています。
前後を比較してパフォーマンスが良くなるのであれば良い、色々考えてこの時はメモ化して、、、とやってもしんどくなるだけなのかなと思います。
(オーバーヘッドでパフォーマンスがーみたいな記事も見るけれど、メモ化でパフォーマンスが最悪になったという話を聞いたことがないので、もはや最初から全てメモ化していってもいい気もする。)
計測をしていく中で、このパターンはこのようなキャッシュのメリットが出る、という知見が得られれば、最適化の最適化戦略も自信の中で構築できるのではないでしょうか。
おそらくそのレベルの人間もいないですし、研究するのも面白そうかなと思っています。

Discussion

ログインするとコメントできます