不思議なもので、少し前に悩んでいたことを解決するためのBabelプラグインにちょうどめぐりあうことができました。
JSXの中身
Reactを書くときに使うJSXですが、実際にJavaScriptとして処理する際にはReact.createElement
という関数(リファレンス)に変換されます。
// before
(<p>
文章
<a href="https://qiita.com/">Qiita</a>
</p>)
// after
React.createElement("p", null,
"文章",
React.createElement("a", { href: "https://qiita.com/" },
"Qiita"
)
);
<br />
のような表記を見ればリテラルのようにも思えますが、実態としては関数呼び出しになっています。さらに、React.createElement
の中身は引数チェックをして、与えられた引数をオブジェクトに詰め込む程度のシンプルなもので、特にキャッシュなどはなされないので、<br />
のようなごくシンプルなVDOMエレメントであっても、呼び出すたびに新しいものが作られます。
毎回生成するデメリット
もちろん、React.createElement
は、本物のDOMの生成と比べれば、ずっと軽い処理です。とはいえ、全く同じ仮想DOMを何度も生成すると、以下のような点で無駄が発生します。
-
React.createElement
の実行時間 - 各回ごとに別なオブジェクトが生成するので、
- メモリを消費し、ガベージコレクタにより負荷をかける
- 比較しても一致しないので、そのVDOMを別なコンポーネントの引数にした場合に
React.memo
が効かなくなる
自分が少し前にした投稿では、最後のデメリットが気になったのでした。
Babelで解決
何気なくBabelのChangeLogを読んでいたところ、@babel/plugin-transform-react-constant-elements
というプラグインの存在を知りました。これは、JSXが同じスコープ内の変数を使っていない場合に。できるだけ上位のスコープ(完全に何も変わらない場合はトップスコープ)まで引き上げる、というものです、
// before
const Hr = () => {
return <hr className="hr" />;
};
// after
const _ref = <hr className="hr" />;
const Hr = () => {
return _ref;
};
手動でこのような作業をやっていたので、喜び勇んでyarn add
したところで、望み通りの結果が得られるようになりました。ただ、(もちろん有害になることではなさそうとはいえ)children
の中身の一部で変化しないエレメントも外側にくくりだされていて、けっこう積極的に処理しているんだなと感じた次第でした。