3.2 JSX の本質 — 関数呼び出しの糖衣
ここで押さえてほしいのは、「JSX は HTML ではなく、JavaScript の関数呼び出しを書きやすくするための 構文拡張 である」という一点です。見た目は HTML にそっくりですが、実体は React.createElement(...) の呼び出しなんですね。
最初は「HTML を書いているつもり」でも進めますが、少しつまずいた時にこの事実を思い出すとだいぶ楽になります。
JSX は何者か
React の公式ドキュメントは JSX を次のように定義しています。
JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file.
つまり JSX は「JavaScript の中に HTML っぽい書き方を混ぜていいよ」という構文上の許可証です。ブラウザは JSX を理解しないので、ビルドツール (Part 3.1 の Vite) が JavaScript の関数呼び出しに変換してからブラウザに送ります。
公式はさらにこうも言っています。
JSX and React are two separate things. They're often used together, but you can use them independently of each other.
出典: 同上
JSX は構文ルールで、React はライブラリです。二つを結びつけているのは、JSX が変換されると React.createElement(...) (あるいは新しい transform では react/jsx-runtime の関数) を呼び出すコードになる、というただの約束だけです。
createElement への変換
次の JSX を見てください。
const element = <h1 className="title">Hello</h1>これを Vite (内部の esbuild / Babel) が変換すると、概ね次のコードになります。
// 変換後 (概念的なイメージ)
const element = React.createElement('h1', { className: 'title' }, 'Hello')createElement のシグネチャは公式に明記されています。
jsconst element = createElement(type, props, ...children)
type— タグ名の文字列 ('h1') またはコンポーネント関数props— 属性のオブジェクト (null可)...children— 0 個以上の子要素
戻り値は「React 要素オブジェクト」です。ただの JavaScript オブジェクトであって、DOM そのものではありません。React は後でこのオブジェクトを見て「ブラウザの DOM をどう変更するか」を決めます。
図 3.2.1 JSX からブラウザへの変換フロー
JSX ↔ createElement を切り替えて見てみる
文章で「変換される」と言われても、最初はピンと来ないですよね。下のデモは、JSX のいくつかのパターンを選ぶと、変換後の createElement 呼び出しが隣に並んで出る、というものです。タブを切り替えて、左右を見比べてみてください。
const el = <h1 className="title">Hello</h1>const el = React.createElement(
'h1',
{ className: 'title' },
'Hello'
)タグ名は文字列に、属性はオブジェクトに、子要素は第 3 引数以降に並びます。
見るポイントは 2 つです。
- タグ名の文字列化 —
<h1>は'h1'という文字列に、<Greeting>(大文字始まり) はGreetingという関数そのものになる - 子が「第 3 引数以降」として並ぶ — 入れ子の JSX は、そのまま引数リストとしてフラットに並ぶ
この見方が染みつくと、「JSX は見た目こそ HTML だけど、書いているのは関数呼び出し」という感覚が馴染んできます。
HTML との違い
JSX は HTML に似ていますが、同一ではありません。公式がまとめている 3 つのルールを押さえておきましょう。
ルール 1 — ルート要素はひとつ
コンポーネントは複数の要素をそのままは返せません。ひとつの親タグで包むか、Fragment (<>...</>) を使います。
// NG: 兄弟をそのまま返している
function Bad() {
return (
<h1>タイトル</h1>
<p>本文</p>
)
}
// OK: Fragment で包む
function Good() {
return (
<>
<h1>タイトル</h1>
<p>本文</p>
</>
)
}Fragment は DOM に余計な要素を足さないので、<div> のラッパが不要な場面で便利です。
ルール 2 — すべてのタグを閉じる
HTML では許されていた <img> や <br> のような省略形は、JSX では使えません。
<img src="/logo.png" alt="ロゴ" />
<br />ルール 3 — 属性は camelCase、予約語は言い換え
HTML の属性は JSX では主に camelCase になります。さらに JavaScript の予約語とぶつかる class / for は別名になっています。
| HTML | JSX |
|---|---|
class="x" | className="x" |
for="id" | htmlFor="id" |
tabindex="0" | tabIndex={0} |
onclick="..." | onClick={handler} |
aria-* と data-* は歴史的事情から、HTML と同じくハイフン区切りのままです (公式が明記しています)。
{式} で JavaScript を埋め込む
JSX の中で { } を書くと、中身が JavaScript 式として評価されます。公式はこう説明しています。
You can only use curly braces in two ways inside JSX:
- As text directly inside a JSX tag
- As attributes immediately following the
=sign
const name = 'React'
const avatar = '/me.png'
function Greeting() {
return (
<div>
<h1>こんにちは、{name} さん</h1>
<img src={avatar} alt={name} />
</div>
)
}ひとつ注意として、src="{avatar}" のようにクォートで囲むと、ただの文字列 "{avatar}" として扱われてしまいます。
オブジェクトを渡すときの二重中括弧
style 属性にオブジェクトを渡すときは、下のコードのように中括弧が 2 重になります。外側が JSX の式の始まり、内側がオブジェクトリテラル、というだけのことです。
<p style={{ color: 'red', fontSize: 16 }}>警告</p>CSS プロパティも camelCase (font-size → fontSize) です。
条件レンダリングとリスト
{ } の中に書けるのは式なので、三項演算子や && や map がそのまま使えます。
type Props = { isLoggedIn: boolean; todos: string[] }
function Panel({ isLoggedIn, todos }: Props) {
return (
<section>
{isLoggedIn ? <span>ようこそ</span> : <a href="/login">ログイン</a>}
{todos.length > 0 && (
<ul>
{todos.map((todo, i) => (
<li key={i}>{todo}</li>
))}
</ul>
)}
</section>
)
}key の意味は 3.7 リストと key で詳しく扱います。
手を動かして確かめる
以下は JSX の基本 (属性、{}、条件分岐、Fragment) を詰め込んだサンプルです。StackBlitz で編集すると、すぐに結果が反映されます。
まとめ — JSX は「関数呼び出しの見やすい書き方」
- JSX は HTML ではなく JavaScript の拡張構文で、ビルド時に
createElement呼び出しに変換される。 - 戻り値は React 要素オブジェクト。DOM そのものではない。
- HTML との差分 (閉じタグ必須、camelCase 属性、
className/htmlFor) は「ブラウザではなく JavaScript の世界のため」の調整。 { }で式を埋め込める。二重中括弧は「外側が式、内側がオブジェクトリテラル」というだけ。
さらに深く学ぶには
- react.dev — Writing Markup with JSX — 3 つのルールと変換例。
- react.dev — JavaScript in JSX with Curly Braces —
{}の使い方のすべて。 - react.dev — createElement — 変換先の関数の完全なリファレンス。