JSXとは、JavaScriptにHTML風の構文を組み込んだ表現方法である。JSとは別物で、トランスパイラでJSに変換しないと使えない。また、React.jsとは独立して使えるらしい。
曖昧な理解で適当に使うと、[object Object]
みたいにうまく表示されない時に困る。というか困っているので、JSXとは何か、というところから基本的な知識をまとめたいと思う。
JSXとは
JSXの仕様のドラフトやWikipediaでは、「JSXとはXML風の拡張構文で、JavaScript XMLの略だ」みたいなことが書かれている。
ただ、Reactの最新のドキュメントでは、以下のような表現が使われている。
JSX lets you write HTML-like markup inside a JavaScript file, keeping rendering logic and content in the same place. Sometimes you will want to add a little JavaScript logic or reference a dynamic property inside that markup.
訳:JSXは、レンダリングロジックとコンテンツを同じ場所に保持し、JavaScriptファイルの中でHTMLライクなマークアップが書けるようにする。そのマークアップの中にJavaScriptのちょっとしたロジックや動的プロパティへの参照も追加できる。
DOMの構造をマークアップする独自の言語という意味では「XML-like」の方が正確である。しかし実際の使用シーンを鑑みると「HTML-like」の方が直感的に伝わる。このために、あえて「HTML-like」に変えたのではないかと思う。
また、JSXはJavaScriptファイルの中に書けるが、ECMAScriptの標準仕様ではない。そのままでは機能せず、BabelやTypeScriptコンパイラなどを使って、標準仕様を満たすJavaScriptにトランスパイルする必要がある。
ツールにとっても開発者にとっても、それがただのJavaScriptファイルか、JSXを含んだJavaScriptファイルかを区別する方法があると便利だ。そのためJSXが含まれたファイルには、.jsx
という拡張子が使われるのが一般的である。
特徴
既存の言語に自前の新しい構文を埋め込むのは厄介である。構文拡張を考えるのが自分ひとりとは限らないし、言語自体が互換性のない構文拡張を行う可能性もある。JavaScriptのような人気言語ではなおさらだ。
新しい構文拡張と共存するために、JSXはシンプルで、ミニマルで、親しみやすい、独立した構文を持っている。
ちなみに、この仕様はXMLやHTMLの仕様に準拠しようとはしていない。JSXはECMAScriptの機能として設計されている。XMLに寄せているのは、親しみやすさのためだけであるらしい。
Reactとの関係
JSXは、もともとReactの一部として開発された。ただ、JSXとReactはそれぞれ独立している。JSXは構文拡張であり、ReactはJavaScriptライブラリである。
React 16までは、JSXをJavaScriptにトランスパイルする際にReactをインポートする必要があった。これは、HTMLのレンダリングにReact.createElement()
メソッドが使われていたためである。
React 17でJSXの置換方法が改善され、React.createElement()
がjsx()
とjsxs()
に分かれた。前者が動的なコンポーネント、後者が静的なコンポーネントの変換を担当する(jsxsの’s’はstaticの’s’)。
これによってReactをインポートする必要がなくなり、構成がちょっとすっきりした。
とはいえJSXをJavaScriptに変換するための関数群(jsx-runtime)はReactのパッケージに含まれており、コンポーネントやフックなどもReactがなければ使えない。
独自にJSXランタイムを開発することは可能であるし、そうしたツールも1つ2つではない(Vue,Preact,TypeScriptコンパイラなど)。ただ、依然としてReactとJSXは切っても切れない関係にあると言える。
JSXの基本的なルール
JSXの基本的なルールをまとめる。
- JSX要素は、必ず単一の親要素でラップされる。
- 不要な要素を使いたくないときは、フラグメント(
<></>
)でラップする。 - 閉じタグを忘れない。単一のタグも
<tagname />
みたいに閉じる。 - 属性名は、ハイフンを使わず、キャメルケースで書く。
class
は、className
、for
は、htmlFor
と書く。aria-*
属性とdata-*
属性はHTMLと同じように書く。- 属性値に文字列を使うときは、
'
か"
で囲う。 - 属性値とタグの中身のテキストは、動的に指定できる。
- 動的な指定は
{ 式 }
の形で行う。{
と}
の間では、JavaScriptの式がすべて使える。 {}
の中にJSX要素の配列変数を置くと、自動で順番にレンダリングされる。- スプレッド構文は、属性値でしか使えない。
- style属性には、文字列を渡せない。ので、オブジェクトを渡す。
{{key:value}}
のように括弧を二重にすると、オブジェクトを渡せる。- 整形などでJXSを改行する際は、
(
と)
で囲うとセミコロンの自動挿入を避けられる。 - JSX内でコメントを書く際には
{/* コメント内容 */}
や{// コメント}
の形式を使う。
捉え方のコツ
箇条書きにするとワサワサしているように見える。しかし、JSXはシンプルな概念である。
裸のHTMLは、JavaScriptでは使えない。document.createElement()
メソッドを使うか、文字列を変換するかしてDOMオブジェクトを作り操作するしかない。
文字列だと簡単に構造を表現できるが、動的な操作が難しい。一方、DOM APIでは動的な操作はできるが、構造を表現するのが難しい。しかも命令を長々と書き連ねなければならない。
JavaScriptの中でHTMLを書き、最後にHTMLの部分をJavaScriptオブジェクトに変換すれば、この問題は解決する。HTMLで動的に変更される可能性がある箇所、つまり属性値とテキストコンテンツにJavaScriptの値を埋め込めるようにすれば、なお使い勝手が良い。
これがJSXである。
コンポーネントやフックはReactの概念で、JSXとは関係ない。JSXは、JavaScriptのコード中に直接書かれた裸の<タグ名 属性名={変数名1}>{変数名2}</タグ名>
みたいな部分である。
私が出会した[object Object]
みたいな表示は、JSXをテンプレート文字列とごっちゃにして扱ったせいだった。そのせいで、JSX要素ではなく、ただのJavaScriptオブジェクトをレンダリングしようとしていた。
多くの場合、JSXでは文字列としてHTMLコードを扱う必要がない。<タグ名 属性名={変数名1}>{変数名2}</タグ名>
のように、裸のHTMLを書けば足りるからだ。
JSXをJavaScriptに変換する方法
JSXをただJavaScriptに変換するだけであれば、以下の手順で終わる。
Babelでトランスパイルする
BabelでJSXをJavaScriptにトランスパイルするには、@babel/plugin-transform-react-jsx
を使う。
続いてBabelの設定ファイル(babel.config.json
)を用意する。@babel/plugin-transform-react-jsx
には、以下のようなオプションがある。
プロパティ名 | 概要 |
---|---|
throwIfNamespace | 真偽値。XMLの名前空間タグが使われた場合に、エラーを投げるか。デフォルトはtrue 。 |
runtime | automatic or classic 。自動でインポート文を挿入するか、しないか。デフォルトはclassic (しない)。 |
importSource | 文字列。インポート文のインポート元ディレクトリ。デフォルトはreact 。 |
pragma | 文字列。変換に使う関数名を指定する。runtime がclassic の場合に有効。デフォZルトはReact.createElement() 。 |
pragmaFrag | 文字列。フラグメントの変換に使う関数名を指定する。runtime がclassic の場合に有効。デフォルトはReact.Fragment() 。 |
useBuiltIns | 真偽値。JSX中でスプレッド構文が使われている時に、プロパティのアサインにObject.assign を使うか、Babelのヘルパー関数を使うか。デフォルトはfalse 。 |
useSpread | 真偽値。JSX中でスプレッド構文が使われている時に、プロパティのアサインにスプレッド構文を使うか、Babelのヘルパー関数を使うか。デフォルトはfalse 。useBuiltIns がtrue の場合でも、こちらが優先される。 |
参考:@babel/plugin-transform-react-jsx · Babel
今回は一番楽な設定を選ぶ。
あとは、適当なJSXファイルを用意するのみである。ここでは以下の内容をscript.jsx
に保存する。
以下のBabelコマンドでトランスパイルする。
出力ファイルはcompiled-script.js
とした。出力内容は、以下のようになる。
JSXランタイムのimport文は、その環境にインストールされているかどうかに関係なく、設定に基づいて自動で挿入される(runtime
オプションがclassic
の場合は手動で書く)。
そのため、上記のコードを実際に使える形にするには、react/jsx-runtime
(とその依存関係)を用意した上でバンドルする必要がある。
これは、他のJSXランタイムを利用する場合も同様である。
メモ
If you use JSX with a library other than React, you can use the importSource option to import from that library instead — as long as it provides the necessary entry points. Alternatively, you can keep using the classic transform which will continue to be supported.
If you’re a library author and you are implementing the /jsx-runtime entry point for your library, keep in mind that there is a case in which even the new transform has to fall back to createElement for backwards compatibility. In that case, it will auto-import createElement directly from the root entry point specified by importSource.
引用元:Introducing the New JSX Transform – React Blog