🐙
Reactのmemo化で再レンダリングが防げるという誤解を解く
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操作は行いません。小規模な画面を構築するレベルなら、効率を気にする必要の無いケースがほとんどです。
大量のデータを表示したり、コンポーネントの階層が深くなることが予見されているなら対処をとっておいた方が良いですが、そうで無いならあまり考えすぎないことをおすすめします。
Discussion
「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>
にすることでハイライトは防げました。貴重な情報ありがとうございます。