React・まず動かしてみる

JSXの読み方——HTMLに見えるけどJavaScript

JSXはHTMLによく似ていますが、正体はJavaScriptです。classNameになる理由、波かっこで値を差しこむ書き方、タグを1つにまとめる決まりなど、HTMLとの違いだけをまとめて確認します。

前回書いたこの1行を、もう一度見てください。

return <h1 className="p-8 text-3xl font-bold text-sky-600">しまちゃんのことりずかん</h1>

JavaScriptの中に、HTMLのタグが引用符もなしで書かれています。これがJSXです。

JSXはHTMLによく似ていますが、正体はJavaScriptです。似ているぶん、違うところでつまずきます。この回でその違いを全部そろえます。

そもそも何に変換されているのか

ブラウザはJSXを読めません。ビルドのときに、こう変換されています。

// 書いたもの
<h1 className="title">しまちゃん</h1>

// 変換されたもの(イメージ)
jsx('h1', { className: 'title', children: 'しまちゃん' })

タグは関数の呼び出しに、属性はオブジェクトになります。classNameって何だ」と思ったら、この形を思い出してください。オブジェクトのキー名なのです。だからJavaScriptの都合が優先されます。

違い①:classclassName

classはJavaScriptの予約語(クラスを作るための言葉)なので使えません。

<div className="card">…</div>

同じ理由で、<label for="…">htmlForになります。この2つ以外はHTMLとほぼ同じです。

HTMLJSX
classclassName
forhtmlFor
onclickonClick
tabindextabIndex

イベントと複数語の属性はキャメルケース(2語目の頭を大文字)になる、と覚えておけば大体当たります。

違い②:値は波かっこで差しこむ

JSXの中でJavaScriptの値を書きたいときは{}で囲みます

const name = 'シマエナガ'

return <h1>{name}のずかん</h1>

属性にも使えます。引用符は付けません

<img src={imageUrl} alt={name} />
<img src="/shima.png" alt="しまちゃん" />   {/* 決め打ちの文字列は引用符でOK */}

波かっこの中に書けるのは(値になるもの)だけです。計算・関数の呼び出し・三項演算子は書けますが、if文やfor文は書けません。

<p>{birds.length}羽います</p>
<p>{name.toUpperCase()}</p>
<p>{isRare ? 'レア' : 'ふつう'}</p>

違い③:返せるタグは1つだけ

これはエラーになります。

return (
  <h1>しまちゃんのことりずかん</h1>
  <p>全8羽をあつめよう</p>
)

関数が返せる値は1つだからです。親で包めば解決します。

return (
  <div>
    <h1>しまちゃんのことりずかん</h1>
    <p>全8羽をあつめよう</p>
  </div>
)

とはいえ、包むためだけの<div>が増えるのは気持ちのいいものではありません。そこで名前のないタグが使えます。

return (
  <>
    <h1>しまちゃんのことりずかん</h1>
    <p>全8羽をあつめよう</p>
  </>
)

<></>フラグメントと呼ばれ、HTMLには何も出力されません。「まとめるためだけの入れ物」です。

違い④:閉じタグは省略できない

HTMLでは<img src="…"><br>で閉じなくても動きましたが、JSXでは必ず閉じます

<img src="/shima.png" alt="しまちゃん" />
<br />
<input type="text" />

末尾の/を忘れると「Unterminated JSX contents」のようなエラーになります。JSXのエラーは大半がタグの閉じ忘れなので、赤い文字が出たらまずタグを数えてください。

違い⑤:コメントは{/* */}

JSXの中では、HTMLの<!-- -->は使えません。

<div>
  {/* ここはコメント */}
  <h1>しまちゃん</h1>
</div>

波かっこでJavaScriptに戻ってから、JavaScriptのコメントを書く——という形です。タグの外(関数の中)ならふつうに//が使えます

組み合わせるとこうなる

ここまでの決まりを全部使うと、鳥のカード1枚はこう書けます。

function App() {
  const title = 'しまちゃんのことりずかん'

  return (
    <div className="min-h-screen bg-sky-50 p-8">
      <h1 className="text-2xl font-bold text-sky-800">{title}</h1>
      <p className="mt-1 text-sm text-slate-500">全8羽をあつめよう</p>

      <div className="mt-6 flex max-w-md gap-4 rounded-2xl bg-white p-5 shadow-sm">
        <span className="text-4xl">🐦</span>
        <div>
          <h2 className="font-bold text-slate-800">シマエナガ</h2>
          <p className="mt-1 text-sm text-slate-600">
            雪のようにまっ白な小鳥。北海道にすんでいる。
          </p>
        </div>
      </div>
    </div>
  )
}

export default App

見出しは{title}で変数から、カードの中身はまだ直接書いています。Tailwindのクラスは、カードの角丸(rounded-2xl)・白背景(bg-white)・影(shadow-sm)・内側の余白(p-5という、いつもの組み合わせです。HTMLに書くのと同じクラスがそのまま使えます

公式ドキュメントも見てみる

JSXの決まりごとは、公式にもまとまったページがあります。「JSXのルール」の節に3つのルールが並んでいるので、迷ったときの答え合わせに使えます。

ja.react.dev外部サイトJSX でマークアップを記述する – React公式の「JSXでマークアップを記述する」。ここで扱った「1つのタグで包む」「閉じタグは必須」「キャメルケース」が、公式の言葉で3つのルールとして整理されています

このレッスンのまとめ

  1. JSXの正体はJavaScript。タグは関数の呼び出しに、属性はオブジェクトに変換される
  2. classclassNameforhtmlFor。イベントはonClickのようにキャメルケース
  3. 値は波かっこ{}で差しこむ。中に書けるのは式だけif文は書けない)
  4. 返せるタグは1つだけ。余計な<div>を増やしたくないときは<></>で包む
  5. 閉じタグは省略できない<img /><br />
  6. コメントは{/* */}

次は、いま1枚だけ書いたカードを部品(コンポーネント)として切り出します。同じものを3枚並べるところまでやると、Reactの組み立て方が見えてきます。

やってみよう:JSXでカードを1枚置く

src/App.jsxを、上のコードのとおりに書きかえます。

  1. src/App.jsxを開き、App関数の中を上のコードに差しかえる
  2. 見出しの文字はconst title = '…'に入れて、波かっこで差しこむ形にする
  3. ブラウザで、白いカードが1枚出ているのを確認する
水色の背景に見出しと白い角丸カードが1枚表示されている
この画面になればOK

うまくいかないときは、classと書いていないか、閉じタグを忘れていないかを見てください。この2つで9割です。

よくある質問

JSXとは何ですか?
JavaScriptの中にHTMLのようなタグを書ける記法です。ブラウザはそのままでは読めないので、ビルドのときにJavaScriptの関数呼び出しへ変換されます。Viteがその変換をやってくれています。
なぜclassではなくclassNameなのですか?
classがJavaScriptの予約語(クラスを定義するための言葉)だからです。同じ理由でforもhtmlForになります。それ以外の属性はHTMLとほぼ同じ名前です。
タグを2つ並べるとエラーになるのはなぜですか?
JSXは最終的に1つの値を返す式だからです。値を2つ同時に返すことはできないので、1つの親要素で包みます。余計なdivを増やしたくないときは、名前のないタグ(フラグメント)で包みます。
JSXの中でifは使えますか?
波かっこの中には式しか書けないので、ifはそのままでは使えません。三項演算子や&&を使うか、returnより前でifを書いて変数に入れておきます。