kk-web

React.memo なんて使わなくて良いよ

2022-09-28

最近ちらほら React.memo について勘違いしているプログラマーと出会うことが何度かあったので、改めて書いていこうと思います。


公式サイトの記述

そもそも公式サイトの記述をきちんと読まず、正しく理解できていない状態で使用しているプログラマーが非常に多い印象があります。

ということで React.memo に関する公式サイト の引用です。

もしあるコンポーネントが同じ props を与えられたときに同じ結果をレンダーするなら、結果を記憶してパフォーマンスを向上させるためにそれを React.memo でラップすることができます。つまり、React はコンポーネントのレンダーをスキップし、最後のレンダー結果を再利用します。 React.memo は props の変更のみをチェックします。React.memo でラップしているあなたのコンポーネントがその実装内で useState、useReducer や useContext フックを使っている場合、state やコンテクストの変化に応じた再レンダーは発生します。 これはパフォーマンス最適化のためだけの方法です。バグを引き起こす可能性があるため、レンダーを「抑止する」ために使用しないでください。

ポイントとしては以下のとおりです。

  • とあるコンポーネント対し、頻繁に同じ props が流れ込んでいる場合、使い時の可能性がある
  • とあるコンポーネントは同じ props が流れ込んできた場合、毎回同じ結果を render している必要がある
  • React.memo はパフォーマンスの最適化のために使用するべきであり、レンダーの抑制目的で使用してはならない
  • React.memo を使用したところで必ずパフォーマンスが良くなる保証はない

もう少しわかりやすい解説

自分も過去さまざまなドキュメントを読んできましたが、最終的に以下のドキュメントが 1 番わかりやすかったです。

Use React.memo() wisely

この ドキュメントの 2 章によると、React.memo は以下のケースで使用すると効果的だと書かれています。

  1. Pure functional component
  2. Renders often
  3. Re-renders with the same props
  4. Medium to big size

1 つずつ解説をば。

1. Pure functional component

Your Component is functional and given the same props, always renders the same output.

コンポーネントは関数型であり、同じ props が与えられ、常に同じ出力をレンダリングします。(Google 翻訳)

これはわかりやすいですね。

2. Renders often

Your Component renders often.

コンポーネントが頻繁にレンダリングされます。(Google 翻訳)

これもわかりやすい、頻繁というのがポイントですね。

3. Re-renders with the same props

Your Component is usually provided with the same props during re-rendering.

通常、コンポーネントには、再レンダリング中に同じ props が提供されます。(Google 翻訳)

再 render 中に同じ props が流れ込んでくるケースで有効ということですね。

文脈から察するに、よほど重いレンダーが走っていそうな雰囲気を受けます。

4. Medium to big size

Your Component contains a detect amount of UI elements to reason props equality check.

コンポーネントには、props の等価性チェックを推論するための量の UI 要素が含まれています。(Google 翻訳)

ちょっとわかりづらいですが、いくら頻繁に同じ props が流れ込んできたとしても、その props をただ表示するだけであれば大したコストにならないということかなーと思います。

props を山程いじくっているようなケースではじめて効果を発揮する、という意味だと思います。

実装イメージ

Use React.memo() wisely に掲載されているのサンプルコードがわかりやすいですね。

映画の視聴回数をリアルタイムで表示するという仕様のもと、以下のようなコンポーネントを定義しました。

function MovieViewsRealtime({ title, releaseDate, views }) {
  return (
    <div>
      <Movie title={title} releaseDate={releaseDate} />
      Movie views: {views}
    </div>
  );
}

この場合、呼び出し側では titlereleaseDate はほぼ固定値ですが、毎秒 views が更新される可能性があります。

// Initial render
<MovieViewsRealtime
  views={0}
  title="Forrest Gump"
  releaseDate="June 23, 1994"
/>
// After 1 second, views is 10
<MovieViewsRealtime
  views={10}
  title="Forrest Gump"
  releaseDate="June 23, 1994"
/>
// After 2 seconds, views is 25
<MovieViewsRealtime
  views={25}
  title="Forrest Gump"
  releaseDate="June 23, 1994"
/>
// etc

そこでもとのコンポーネントに対し、ほぼ固定値である titlereleaseDate に関わっているコンポーネントを切り出し、memo 化すると、パフォーマンスは上がる可能性があるいうことですね。

function MovieViewsRealtime({ title, releaseDate, views }) {
  return (
    <div>
      <MemoizedMovie title={title} releaseDate={releaseDate} />
      Movie views: {views}
    </div>
  );
}

冷静に考えてみて

上記で上げられた条件を満たすコンポーネントってなかなか存在しないです。

サンプルのコードと同様、よほどリアルタイム性が求められて、かつ巨大なコンポーネントってなると、どういうケースが当てはまるんでしょうか。

ぱっと思いつく限りでは、以下のような感じですかね?

  • 株・FX などリアルタイムで推移を描画する必要がある
  • YouTube Live のチャットなど、リアルタイムでコメントを反映する必要がある

とはいえ、通常のサイトやサービスであればまー使うケースはなかなかないと思います。

最後に

よく勘違いしている人が多いのですが「render の回数が減った」=「パフォーマンスが上がった」という等式は成り立ちません。

render の回数が減ったところで、props の比較に時間がかかり描画に時間がかかるようでは本末転倒も良いところです。

公式サイトと Use React.memo() wisely の両方に書かれていますが、React.memo を使用するのであれば、必ずパフォーマンスの測定 は行うようにしましょう。


過去に 1 度似たような記事を書きましたが、改めて書いてみました。

自分の認識は以前より変わらず、基本的には使う必要がない機能だと思っています。

React.memo を使う前に、もっと見直すべき実装があるんじゃないか?と言いたくなるケースは決して少なくありません。

React.memo はパフォーマンスの向上における本当の最終手段くらいの認識で、普段は存在を忘れちゃっても何ら問題ないかと。

そんな感じです。

© 2018 kk-web