TypeScriptのJSX設定:その「一行」がアプリケーションの寿命を決める
TypeScriptで開発をしていると、`tsconfig.json`の`compilerOptions`にある`jsx`周りの設定は、つい「とりあえず動くから」と疎かにしがちだ。だが、ここを理解していないということは、エンジンの内部で何が起きているかを知らずに車を走らせているようなものだ。
JSXはもはや単なる「Reactの構文」ではない。VNode(仮想DOM)を生成するための抽象インターフェースであり、そのトランスパイル戦略一つで、バンドルサイズ、メモリ効率、そしてレンダリングのクリティカルパスにまで無視できない影響を及ぼす。
今日は、JSX設定の深淵に触れ、なぜ設定次第でアプリケーションが「爆速」にも「鈍重」にもなるのか、そのメカニズムを紐解いていく。
—
1. JSX設定の全容:コンパイラの裏側で起きていること
まず、基本の`jsx`オプションだ。
{
“compilerOptions”: {
/
“preserve”: JSXを変換せずそのまま出力。主にBabelに任せる場合に使う。
“react”: React.createElementに変換。レガシーだが安定。
“react-jsx”: React 17以降の新しいJSX変換(自動インポート)。
“react-jsxdev”: 開発専用の新しい変換。デバッグ情報が付与される。
/
“jsx”: “react-jsx”
}
}
なぜ `react-jsx` を選ぶべきか
従来の `react` 設定(`React.createElement`)は、すべてのJSXファイルで `import React from ‘react’` を必要とした。これは「使っていないのにReactをインポートする」という無駄な依存を強制する。一方、`react-jsx` はコンパイラが自動的に `_jsx` 関数をインポートするため、名前空間の汚染を防ぎ、バンドルサイズをわずかだが確実に削減できる。
—
2. フレームワーク別:最適化のパラダイム
フレームワークによって、JSXをどう処理すべきかは劇的に異なる。
React (Runtime: Automatic)
React 17以降は、`jsxImportSource` を指定して `react/jsx-runtime` を利用するのが正攻法だ。
{
“compilerOptions”: {
“jsx”: “react-jsx”,
“jsxImportSource”: “react” // 自動インポートのソース元
}
}
Vue 3 (JSXプラグインの併用)
Vueは標準ではテンプレート構文を推すが、JSXを使うなら `@vue/babel-plugin-jsx` が必須だ。TypeScript側では、Vueの型定義を正しく読み込ませる必要がある。
{
“compilerOptions”: {
“jsx”: “preserve”, // TypeScriptに触らせず、Babelに丸投げするのがVueの流儀
“jsxImportSource”: “vue”
}
}
注意点: VueのJSXはReactと異なり、コンポーネントのプロパティ伝播(Prop Drilling)においてメモリ効率が悪化しやすい。`v-model` のようなテンプレート特有の糖衣構文が使えないため、レンダリング負荷を抑えるなら、過度なJSX化を避け、パフォーマンスクリティカルな箇所だけを最適化するのが賢い選択だ。
SolidJS (最強のメモリ効率)
SolidJSのJSXは、Reactのような「仮想DOMの差分比較」を行わない。JSXが直接DOM操作の命令にコンパイルされるため、メモリ使用量は極めて低い。
{
“compilerOptions”: {
“jsx”: “preserve”,
“jsxImportSource”: “solid-js”,
“jsxFactory”: “createElement”, // 必要に応じて調整
“jsxFragmentFactory”: “Fragment”
}
}
SolidJSを使うなら、`jsxImportSource` を適切に設定することで、ランタイムのオーバーヘッドをほぼゼロにできる。これが、ReactからSolidへ移行した際に感じる「吸い付くようなUI」の正体だ。
—
3. 実践:アーキテクトが気にする「非同期の競合」と「バグ回避」
JSX設定が原因で発生する最も厄介なバグは、型定義の不整合によるランタイムエラーだ。特に、`jsxFactory` を独自に設定している場合に多い。
独自Factoryを利用する場合のアンチパターン
もしあなたが独自のライブラリを作っているなら、`jsxFactory` を使うことになるだろう。その際、以下の設定を忘れてはいけない。
{
“compilerOptions”: {
“jsx”: “react”,
“jsxFactory”: “myCustomFactory”, // 独自のVNode生成関数
“jsxFragmentFactory”: “myCustomFragment” // Fragmentのショートカット
}
}
ここで重要なのは、「レンダリング関数内の非同期処理の競合」だ。
多くのエンジニアは `jsxFactory` 内で非同期データを解決しようとするが、JSXは同期的に評価されるべきものだ。ここで `Promise` が混入すると、レンダリングサイクルが崩壊し、メモリリークの温床となる。
解決策:
JSXはあくまで「構造の定義」に徹し、データの解決(Suspenseなど)はランタイム側の責任に分離せよ。`tsconfig.json` で型安全性を高め、`JSX.Element` を厳格に制御することで、コンパイル時に「無効なレンダリング構造」を弾くことができる。
—
最後に:完璧な環境構築とは
TypeScriptのJSX設定は、単なる設定ファイルではない。それはあなたが「どのエンジンに、どうやってDOMを構築させるか」という設計思想の表明だ。
- React: `react-jsx` で余計な依存を断つ。
- Vue: `preserve` でBabelの最適化能力を活かす。
- SolidJS: `jsxImportSource` でランタイムの負荷を極限まで削る。
これらを使い分ける視点を持つだけで、あなたの書くコードは、単なる「動くもの」から「計算資源を敬う、堅牢なプロダクト」へと昇華する。
エディタを開き、今すぐ `tsconfig.json` を見直してほしい。そこに潜む無駄な設定が、実はあなたのアプリケーションのボトルネックである可能性は、決して低くないのだから。

コメント