React のコードを初めて見ると、JavaScript の関数の中に <div> や <button> といった HTML そっくりのマークアップが書かれていて驚くはずです。これが JSX と呼ばれる記法です。HTML に似ていますが実体は JavaScript なので、属性名が微妙に違ったり、値の渡し方に独自のルールがあったりします。この記事では、JSX とは何か、HTML と何が違うのか、波括弧での式の埋め込み、属性への値の渡し方、複数要素を返すときの決まり、コメントの書き方までを順に解説します。React を書き始めたばかりの方が最初に押さえるべき構文をまとめた内容です。
目次
JSX とは JavaScript の構文拡張
JSX(JavaScript XML)は、JavaScript の中に HTML のようなマークアップを直接書けるようにする構文拡張です。ブラウザは JSX をそのまま理解できないので、Babel や TypeScript、Vite が使う SWC などのコンパイラが、ビルド時に通常の JavaScript の関数呼び出しへ変換します。つまり JSX は「見た目が HTML に似ているだけの JavaScript の式」です。
// 自分が書くコード
function Greeting() {
return <h1 className="title">こんにちは</h1>;
}
// ビルド後はおおよそこうなる(React 17 以降の新しい JSX 変換)
import { jsx as _jsx } from 'react/jsx-runtime';
function Greeting() {
return _jsx('h1', { className: 'title', children: 'こんにちは' });
}
変換後を見ると分かるように、JSX が返しているのは DOM 要素そのものではなく、「どんな要素をどんな属性で作るか」を表したただのオブジェクトです。これを React 要素と呼びます。実際の DOM を組み立てるのは React 側の仕事なので、JSX を書く側は「画面をどう見せたいか」だけを書けばよい、というのがこの記法の狙いです。
JSX は式なので、変数に入れたり、関数の引数として渡したり、配列に詰めたりできます。const el = <p>テキスト</p>; のような書き方も普通に成立します。なお JSX を使うファイルの拡張子は、JavaScript なら .jsx、TypeScript なら .tsx にします。TypeScript では拡張子が .ts のままだと JSX 構文が解釈されずエラーになるので注意してください。
HTML とは違う属性の書き方
JSX の中身は JavaScript なので、HTML の属性名をそのまま使えない場合があります。代表的なのが class です。JavaScript ではクラス構文で使う予約語なので属性名に使えず、代わりに DOM のプロパティ名に合わせた className を書きます。同じ理由で label の for 属性は htmlFor になります。
複数語からなる属性は onclick ではなく onClick、tabindex ではなく tabIndex のようにキャメルケースで書きます。ただし data-* と aria-* だけは例外で、HTML と同じくハイフン区切りのまま書きます。
| HTML | JSX | 備考 |
|---|---|---|
class | className | class が JavaScript の予約語のため |
for | htmlFor | for も予約語(ループ構文) |
onclick | onClick | イベントはすべてキャメルケース |
tabindex | tabIndex | 複数語の属性はキャメルケース |
maxlength | maxLength | 同上 |
data-id | data-id | カスタムデータ属性はそのまま |
aria-label | aria-label | ARIA 属性もそのまま |
もうひとつ HTML と大きく違うのが、すべてのタグを閉じなければならないという点です。HTML では <br> や <img src="..."> のように閉じタグを省略できますが、JSX では <br />、<img src="..." /> と自己終了の形にする必要があります。閉じ忘れると構文エラーになります。
function SearchForm() {
return (
<form className="search">
{/* for ではなく htmlFor */}
<label htmlFor="keyword">キーワード</label>
<br />
{/* 単独タグも必ず / で閉じる */}
<input id="keyword" type="text" maxLength={20} />
<button type="submit" onClick={handleClick}>
検索
</button>
</form>
);
}
タグ名の大文字と小文字にも意味があります。<div> のように小文字で始まるタグは HTML の要素として扱われ、<Greeting /> のように大文字で始まるタグは自作コンポーネントとして扱われます。コンポーネント名を小文字で書くと React は未知の HTML タグだと解釈してしまうため、コンポーネントは必ず大文字始まりで定義してください。
波括弧で JavaScript の式を埋め込む
JSX の中で波括弧 { } を書くと、「ここから JavaScript の式です」という合図になります。変数の値、計算結果、関数の戻り値などをそのまま画面に差し込めます。
function Receipt() {
const name = '山田';
const price = 1200;
const count = 3;
const formatYen = (value) => `${value.toLocaleString()}円`;
return (
<div>
{/* 変数をそのまま埋め込む */}
<p>{name}さんのご注文</p>
{/* 計算式も書ける */}
<p>小計: {price * count}</p>
{/* 関数の呼び出し結果も書ける */}
<p>合計: {formatYen(price * count)}</p>
{/* 三項演算子は式なので使える */}
<p>{count > 0 ? '在庫あり' : '品切れ'}</p>
</div>
);
}
ここで重要なのが、波括弧の中に書けるのは式(expression)だけで、文(statement)は書けないという点です。式とは「評価すると値になるもの」で、price * count や formatYen(x)、三項演算子がこれにあたります。一方 if 文や for 文は値を生まない「文」なので、波括弧の中にそのまま書くことはできません。
条件によって表示を変えたいときは、三項演算子や && 演算子といった式で書き換えるか、return より前の場所で if 文を使って変数に入れておく、という方針になります。繰り返し表示も同様で、for 文ではなく戻り値が配列になる map() を使います。
function Status({ state }) {
// JSX の外側なら if 文を普通に書ける
let message;
if (state === 'loading') {
message = '読み込み中';
} else if (state === 'error') {
message = 'エラーが発生しました';
} else {
message = '完了';
}
return <p>{message}</p>;
}
属性に値を渡すときの2つの書き方
属性へ値を渡す方法は2通りしかありません。固定の文字列ならクォートで囲み、それ以外はすべて波括弧で囲む、これだけです。数値・真偽値・配列・オブジェクト・変数・関数は、すべて波括弧を使います。
function Avatar({ user, size }) {
return (
<img
className="avatar" // 固定の文字列はクォートで囲む
src={user.imageUrl} // 変数は波括弧
alt={`${user.name}のプロフィール画像`} // テンプレートリテラルも波括弧
width={size} // 数値も波括弧。width="80" だと文字列として渡る
draggable={false} // 真偽値も波括弧
/>
);
}
間違えやすいのが width="{size}" のようにクォートと波括弧を両方書いてしまうケースです。これは「波括弧を含んだ文字列」として扱われ、意図した値になりません。どちらか一方だけを使ってください。
真偽値の属性には省略記法があります。<button disabled> のように値を省くと disabled={true} と同じ意味になります。逆に false を渡したいときは省略できないので、disabled={isDisabled} のように明示的に書きます。
オブジェクトを渡す場面では波括弧が2重になります。style={{ color: 'red' }} の外側は「JavaScript の式」を表す波括弧、内側はオブジェクトリテラルの波括弧です。見た目が特殊な構文のようですが、単に波括弧が入れ子になっているだけだと理解しておくと迷いません。
複数の要素を返すときは1つの親で包む
JSX は式なので、返せる値は1つだけです。そのため、並んだ複数の要素をそのまま return することはできず、必ず1つの親要素で包む必要があります。包まずに書くと「JSX expressions must have one parent element.」といったエラーになります。
// エラーになる例:兄弟要素を並べて返している
function Profile() {
return (
<h1>山田太郎</h1>
<p>フロントエンドエンジニア</p>
);
}
// div で包めば OK
function Profile2() {
return (
<div>
<h1>山田太郎</h1>
<p>フロントエンドエンジニア</p>
</div>
);
}
とはいえ、レイアウトの都合上いらない <div> を増やしたくないこともあります。その場合は Fragment という空タグ <>...</> を使うと、DOM に余分な要素を出さずに複数要素をまとめられます。使い方は <div> をそのまま置き換えるだけです。
function Profile3() {
return (
<>
<h1>山田太郎</h1>
<p>フロントエンドエンジニア</p>
</>
);
}
また、return の後ろで改行して JSX を書くときは、上の例のように丸括弧 ( ) で囲むのが定石です。囲まずに return だけを書いて改行すると、JavaScript の自動セミコロン挿入によって return; と解釈され、undefined が返って何も表示されなくなります。
JSX の中にコメントを書く
JSX の中では HTML の <!-- --> は使えません。要素の中身にコメントを入れたいときは、波括弧で JavaScript のブロックコメントを囲んだ {/* コメント */} という形にします。これは「コメントだけを含む JavaScript の式」を埋め込んでいる、と考えると納得しやすいはずです。
function Panel() {
// JSX の外側では普通の行コメントが使える
const title = 'お知らせ';
return (
<section>
{/* 要素の中ではこの形で書く */}
<h2>{title}</h2>
{/*
複数行に分けても問題ない
*/}
<p>本文</p>
</section>
);
}
タグの属性を書き並べている途中であれば、波括弧なしで // ... の行コメントを挟むこともできます。ただし書ける場所が限られて混乱しやすいので、JSX の中では {/* */} に統一しておくのが無難です。
書いたはずの内容が画面に出ないとき
JSX は文法エラーにならないのに何も表示されない、というつまずき方をしがちです。原因はほとんどが「波括弧の中に何を書いたか」に集約されます。代表的なパターンを見ていきます。
波括弧の中に if 文や for 文を書いている
{ if (isOpen) { ... } } や { for (const item of items) { ... } } と書くと、「Unexpected token」などの構文エラーになります。前述のとおり波括弧には式しか書けないためです。条件分岐は三項演算子や && に置き換えるか、return の前で if 文を使って変数に結果を入れておきます。繰り返しは items.map(...) のように、配列を返すメソッドを使ってください。
false・null・undefined は何も描画されない
React は false、true、null、undefined を描画対象から除外し、何も出力しません。これは {isLoggedIn && <p>ようこそ</p>} のような書き方を成立させるための仕様ですが、意図せず値が undefined になっているときも同じく「無言で消える」ため、原因に気づきにくくなります。表示されない箇所があったら、埋め込んだ変数の中身をコンソールで確認するのが近道です。
0 だけは画面に出てしまう
数値の 0 は描画されない値には含まれず、そのまま文字として表示されます。{items.length && <List />} と書いたとき、配列が空だと 0 && ... の結果が 0 になり、リストの代わりに「0」という文字が画面に出てしまいます。{items.length > 0 && <List />} のように、左辺を必ず真偽値にするのが確実な対処です。
オブジェクトをそのまま埋め込んでいる
{user} のようにオブジェクトを直接書くと、「Objects are not valid as a React child」というエラーになります。React は文字列・数値・React 要素・それらの配列しか子要素として受け付けないためです。{user.name} のようにプロパティを取り出すか、確認目的なら {JSON.stringify(user)} で文字列に変換して表示します。日付を表す Date オブジェクトも同じ理由でそのままは描画できないので、toLocaleDateString() などで文字列にしてから渡してください。
まとめ
JSX は JavaScript の構文拡張で、ビルド時に React 要素を作る関数呼び出しへ変換されます。HTML に似ていますが中身は JavaScript なので、class は className、for は htmlFor になり、複数語の属性は onClick や tabIndex のようにキャメルケースで書きます(data-* と aria-* は例外)。<br /> のようにすべてのタグを閉じる必要がある点も HTML との違いです。値を埋め込むときは波括弧を使いますが、書けるのは式だけで if 文や for 文は書けません。属性は固定文字列ならクォート、それ以外は波括弧、というルールで覚えておけば迷わないはずです。複数の要素を返すときは1つの親要素か Fragment で包み、コメントは {/* */} の形で書きます。表示が出ないときは、false や null が描画されない一方で 0 は表示されること、オブジェクトはそのまま描画できないことを思い出してみてください。