🐙

Reactのmemo化で再レンダリングが防げるという誤解を解く

2021/05/08に公開2

memo化について

Reactのコンポーネントをmemo化するとき、再レンダリングを防ぐという記述を見かけることがあります。それは誤解であり、実際にmemo化で出来るのは以下のようになものです。

  • 関数の再評価(ロジックの再実行)は防げる
  • 仮想DOMの差分比較は防げない
  • 最終的なレンダリングは確定した仮想DOMの状態次第なので、memo化に関係なく差分があるかどうかでDOM操作が決定する

Reactは仮想DOMの値に変化があった場合に、DOMを書き換える構造になっています。memo化は仮想DOMの生成結果をキャッシュしているに過ぎません。親コンポーネントが再評価されれば、子コンポーネントはmemo化の可否に関わらず差分比較の対象です。

簡単な例

ソースコード

import React, { memo, useState } from 'react';

const Test1 = memo(() => <div>Test1</div>);
const Test2 = memo(() => <div>Test2</div>);
const App = () => {
  const [value, setValue] = useState<string>();
  return (
    <>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <Test1 />
      <Test2 />
    </>
  );
};
export default App;

説明

子コンポーネントはmemo化しており、パラメータもないので再利用時に何の変化もおきません
親コンポーネントはinputのテキストが変化するごとに再評価の対象になります
さてReact Developer Toolsで、再レンダリングの対象を確認してみます

再レンダリングの対象を確認

見ての通り、親コンポーネントが変化すれば、子コンポーネントも再レンダリングの対象になります。memo化は無関係です。

再レンダリングを抑制するには

状態が変化するコンポーネントの下には、極力、子コンポーネントを置かないことです。もしくはコンポーネント間の通信をContextAPIやReduxを使って行い、親コンポーネントの変化を防ぐことが重要です。

再レンダリング抑制が本当に必要なのか考える

Reactは仮想DOMから実DOMへの差分変換を効率的に行っています。再レンダリングとは言っても、必要の無いDOM操作は行いません。小規模な画面を構築するレベルなら、効率を気にする必要の無いケースがほとんどです。

大量のデータを表示したり、コンポーネントの階層が深くなることが予見されているなら対処をとっておいた方が良いですが、そうで無いならあまり考えすぎないことをおすすめします。

GitHubで編集を提案

Discussion

tktk

「React.memoで再レンダリングが防げるというのは誤解だ」とする根拠はReact Developer ToolsでのHighlight updates when components renderの出力結果のみでしょうか?つまり、親コンポーネントの再レンダリングに合わせてTest1とTest2がハイライトされているという結果のみを根拠とされていますか?こちらのissueによると、memo化したにもかかわらずハイライトがあたってしまうのはReact.Fragmentの挙動によるものです。ご提示のコードではReact.Fragmentを使用しているので、React.memoによって実際には子コンポーネントが再レンダリングされていないにもかかわらず、ハイライトがあたってしまっているようです。ご提示のコードに対してProfilerを使用してみるとTest1とTest2がハイライトされている状態であっても、どちらもDid not render.という結果で再レンダリングが防げていることが確認できます。React.Fragmentの代わりにdivを使用すればハイライト結果がレンダリング結果と一致するようになります。もし、別の根拠があればご提示いただけると幸いです。

空雲空雲

issuesの内容の通り、<></><div></div>にすることでハイライトは防げました。

  • 関数の再評価(ロジックの再実行)は防げる ← 再実行されないのは今まで通り
  • 仮想DOMの差分比較は防げない ← フラグメントで並列に展開しなければ防げるためハイライトされない

貴重な情報ありがとうございます。