結論
JSX式 ( <MyComponent /> ) のほうをメモ化すればReact.memoは必要ないです。
背景
皆さんこんにちは。ReactにおいてはReact Compilerの利用が推奨されており、これを使うことで手動でのメモ化が不要になるとされています。つまり、useMemo, useCallback, そして React.memo が不要になるということです。
公式ドキュメントにも以下のように書かれています。
React Compiler の機能について知り、React アプリケーションを自動的に最適化して
useMemo、useCallback、React.memoによる手動メモ化を不要にしてくれる仕組みについて理解しましょう。
そうなると、実際にReact Compilerがどのようにコードを変換するのか、調べたくなりますね。React Compiler Playgroundを使えば簡単に調べられます。以下は、デフォルトのサンプルの変換前と変換後です。
function MyComp({ css }) {
const today = new Date()
return <div>{JSON.stingify(today)}</div>
}
export default function MyApp() {
return <MyComp css={{display: "block"}} />;
}
import { c as _c } from "react/compiler-runtime";
function MyComp(t0) {
const $ = _c(1);
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const today = new Date();
t1 = <div>{JSON.stingify(today)}</div>;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
}
export default function MyApp() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <MyComp css={{ display: "block" }} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
変換後のコードがどういうものなのか、細かい解説は他の記事に譲ります。ざっくり言うと、$がメモ化されたデータを保持しており、if文がデータの再計算が必要かどうかを判定しています。
このような変換がされるのであれば、useMemo(やuseCallback)相当の最適化がされていることは納得できるでしょう。しかし、React.memoはどうなのでしょうか。上記のコードを見ても、MyCompやMyAppがReact.memoで自動的にラップされたようには見えません。これだけでReact.memo 相当の最適化がされたことになるのかは自明ではありませんね。
React.memoが不要になる仕組み
結論に書いてあるとおり、実はJSX式の結果自体をメモ化することで、React.memoと同等の効果を得られます。
理解しやすくするために、今回のサンプルに対して、React Compilerと同等の最適化を手動でやってみましょう。すると、こうなります。
function MyComp({ css }) {
const t = useMemo(() => {
const today = new Date()
return <div>{JSON.stingify(today)}</div>;
}, []);
return t;
}
export default function MyApp() {
const t = useMemo(() => {
return <MyComp css={{display: "block"}} />
}, []);
return t;
}
ポイントは、returnするJSX式そのものがuseMemoでメモ化されている点です。
そのため、MyAppが2回目以降レンダリングされた場合、返り値tは同じオブジェクト(TypeScriptの型で言うとReactElement)になります。
実は、ReactはReactElementに対してレンダリング結果がキャッシュされているようです。そのため、こうすることで2回目以降MyCompは呼び出されることすらなく、React.memoと同等の効果が得られます。
このReactElementへのキャッシュの機構により、React Compilerの仕組みがシンプルになっています。useMemo(とuseCallback)相当の自動適用と、React.memo相当の自動適用を共通の仕組みで行うことができています。
まとめ
この記事では、React CompilerがReact.memo(相当の処理)を自動的に行う仕組みを解説しました。
まだReact Compilerを導入できない環境に置かれている方にとっては、これは実はReact.memoを使わなくてもuseMemoを使って同じような効果が得られることを意味しています。
場合によっては、React.memoよりもuseMemoを使った方がすっきりする場面もあるでしょう。例えば、useMemoなら依存配列が要素1つだけどprops比較だと10個とかになってしまう場合です。たまに使えるテクニックです。