3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Ateam Group U-30Advent Calendar 2022

Day 20

「ここはメモ化した方がいいですよ」「ほんとかなぁ?」

Last updated at Posted at 2022-12-25

はじめに

自分があまりuseMemoを使ったことがない時に「 ここはメモ化すると良さそうですよ 」とレビューをもらいましたが
「 useMemoを使用すると渡した引数が変更されたタイミングで再計算が行われるけれど、どのくらい改善するんだ? 」と改善幅などがわかってませんでした

実際にuseMemoを使用することでどのくらいレンダリング時間が改善するのか
どうやって改善幅を確認するのかというところを調べたので残そうと思いました

useMemoについて

まずそもそものuseMemoについてですが
こちらの記事でわかりやすく解説していただいていますので自分の方ではザッと要点だけまとめます

useMemoを使用する時は
第一引数に保持しておきたい処理
第二引数に処理が依存しているデータの配列を渡します

// 保持しておきたい処理は a+bの結果を返す関数
// 依存しているデータはaとbなので配列で渡す
const memoizedValue = useMemo(() => a + b , [a, b])

上記の内容であれば「最初に処理が呼ばれる時もしくはa,bの値が変わらなければメモ化したaとbの和を返す」となります。

再レンダリングされるとき

こちらはuseStateの説明の一部になりますが
setState関数で値を更新する度に再レンダリングを実行しようとします

setState 関数は state を更新するために使用します。新しい state の値を受け取り、コンポーネントの再レンダーをスケジューリングします。

こちらのコードだとボタンを押す度にstateが更新されていきます
先ほどの説明通り、setStateを使用して値を更新する度にこのコンポーネントは再レンダリングが実行されるためnonMemoizedValueは毎回計算処理が実行されることとなります
逆にmemoizedValueは依存している変数に変更がないため再計算は行われません

const memoizedValue = useMemo(() => a + b , [a, b])
const nonMemoizedValue = () => { return a + b }
const [state, setState] = useState(0)
return(
  <div>
    <p>{memoizedValue}</p>
    <p>{nonMemoizedValue}</p>
    <button onClick={()=>setState(state + 1)}>{memoizedValue}</button>
</div>
)

簡単なサンプルなので実際にはこの程度でuseMemoを使用するのは役に立ちませんが
useMemoの使用する理由や使い所はなんとなく想像できたかと思います

useMemoで本当に効果があるか確認する

「メモ化した方がいいですよ」とレビューもらった時に「効果を確認する術があればな...」と思ったらReactからProfilerAPIが提供されているのでそちらがうまく使えそうだなと思い調べてみました。

ProfilerAPIについて

使用方法としては下記の様に検証したいコンポーネントを子要素として設定して
propsとしてこちらの2つを渡します

  • 識別子の役割になるid
  • コンポーネントが更新されたタイミングで実行されるonRenderCallback
<Profiler id="test" onRender={callback}>
  <TodoList />
</Profiler>

このcallbackの中に子要素として設定したコンポーネントの描画にかかった時間などの情報が渡されます
今回は必要なものだけ抜粋します

  • id - このコールバックが呼び出されたProfilerに指定したid
  • phase - ツリーがマウントされた場合か、再レンダリングか
  • actualDuration - 今回の更新でProfiler含めてレンダリングにかかった時間

他の値についてはコチラにすべて載っていますので必要であればご参考ください :pray:

実際に使用する場合はこの様にログなどに出力して確認します
TodoListというコンポーネント内にてuseMemoを使うか、使わないかという想定のコードになります

const callback = (id, phase, actualDuration) => {
  console.log({ id, phase, actualDuration })
}
return(
  <Profiler id="test" onRender={callback}>
    <TodoList />
  </Profiler>
)

useMemoあり・なしでデータを更新した結果がこちらになります

useMemoなし
1 {id: 'test', phase: 'update', actualDuration: 7.4}
2 {id: 'test', phase: 'update', actualDuration: 2.0}
3 {id: 'test', phase: 'update', actualDuration: 1.6}
4 {id: 'test', phase: 'update', actualDuration: 2.2}
5 {id: 'test', phase: 'update', actualDuration: 2.1}
6 {id: 'test', phase: 'update', actualDuration: 2.2}
useMemoあり(4以降がuseMemoが依存しているデータの更新)
1 {id: 'test', phase: 'update', actualDuration: 1.3}
2 {id: 'test', phase: 'update', actualDuration: 0.5}
3 {id: 'test', phase: 'update', actualDuration: 0.4}
4 {id: 'test', phase: 'update', actualDuration: 6.6}
5 {id: 'test', phase: 'update', actualDuration: 1.9}
6 {id: 'test', phase: 'update', actualDuration: 2.3}

今回は意図的にuseMemoを使用している処理を重くしているので違いがわかりやすいですね

  • useMemoなし
    • 初回実行に一番時間がかかっている
    • どのデータを更新しても再計算が走る
  • useMemoあり
    • 最初の3回はuseMemoが依存していないstateの更新であったため、再計算が行われない
    • 4回目以降依存しているstateを変更したためuseMemoなしと同じくらい時間がかかっている

終わりに

普段メモ化を意識してませんでしたがレビューでもらったことをきっかけに調べていくと数値として変化を確認できること、正しく使用すれば改善されることを知れてよかったです

今回はProfilerAPIについて紹介しましたが、React Developer ToolsでもProfilerという項目があるのでそちらでも似た様なことができそうですね

参考

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?