JSX でマークアップを記述する

JSX とは JavaScript の拡張であり、JavaScript ファイル内に HTML のようなマークアップを書けるようにするものです。コンポーネントを書く手段はほかにも存在しますが、ほとんどの React 開発者は JSX の簡潔さを好んでいるため、ほとんどのコードベースで JSX が使われています。

このページで学ぶこと

  • React がマークアップとレンダリングロジックを混在させる理由
  • JSX と HTML の違い
  • JSX で情報を表示する方法

JSX: JavaScript にマークアップを入れ込む

これまで、ウェブは HTML、CSS そして JavaScript を使って作られてきました。長年にわたり、ウェブ開発者はコンテンツは HTML で書き、デザインは CSS で書き、ロジックは JavaScript で書き、そして大抵の場合はそれらを別ファイルにしていました。コンテンツが HTML 内にマークアップされる一方で、そのページのロジックは別ファイルの JavaScript に存在していました。

紫の背景の HTML マークアップ。p と form の 2 つの子タグを含んでいる。
紫の背景の HTML マークアップ。p と form の 2 つの子タグを含んでいる。

HTML

黄色の背景に 3 つの JavaScript のハンドラ。onSubmit, onLogin, onClick の 3 つ。
黄色の背景に 3 つの JavaScript のハンドラ。onSubmit, onLogin, onClick の 3 つ。

JavaScript

しかしウェブがよりインタラクティブなものになるにつれ、ロジックがコンテンツの中身をも決めるようになっていきました。JavaScript が HTML の領分も担当するようになったのです! これが、React ではロジックとマークアップを同じ場所、すなわちコンポーネントに書く理由です。

前述の例の HTML と JavaScript がミックスされた React コンポーネント。関数名は Sidebar であり、内部で isLoggedIn を呼び出している(黄色)。その中に、前述の例の p タグや、後で示すコンポーネントを呼び出すための Form タグがネストされている(紫)。
前述の例の HTML と JavaScript がミックスされた React コンポーネント。関数名は Sidebar であり、内部で isLoggedIn を呼び出している(黄色)。その中に、前述の例の p タグや、後で示すコンポーネントを呼び出すための Form タグがネストされている(紫)。

Sidebar.js React コンポーネント

前述の例の HTML と JavaScript がミックスされた React コンポーネント。関数名は Form であり、onClick と onSubmit という 2 つのハンドラが含まれている(黄色)。ハンドラの後に HTML が続く(紫)。HTML 部分に、onClick プロパティの設定された input 要素がネストされている form 要素が含まれている。
前述の例の HTML と JavaScript がミックスされた React コンポーネント。関数名は Form であり、onClick と onSubmit という 2 つのハンドラが含まれている(黄色)。ハンドラの後に HTML が続く(紫)。HTML 部分に、onClick プロパティの設定された input 要素がネストされている form 要素が含まれている。

Form.js React コンポーネント

ボタンのレンダリングロジックとマークアップを同じ場所に書くことで、それらが毎回の編集時に同期されることが保証されます。逆に、ボタンのマークアップとサイドバーのマークアップといった互いに関係のない詳細は、互いに分離されるようになるため、それぞれをより安全に独立して更新できるようになります。

個々の React のコンポーネントは JavaScript の関数であり、React がブラウザに表示するためのマークアップを含めることができます。そのマークアップを表現するのに、React コンポーネントは JSX と呼ばれる拡張構文を使用します。JSX は HTML ととてもよく似ていますが、より構文が厳密であり、動的な情報を表示することができます。理解するには、HTML マークアップを JSX マークアップへと変換してみるのが最もよいでしょう。

補足

JSX と React は別の物です。一緒に使われることが多いですが、片方だけを独立して使うことは可能です。JSX とは言語の拡張であり、React は JavaScript ライブラリです。

HTML を JSX に変換する

このような(まったく正しい)HTML があるとしましょう:

<h1>Hedy Lamarr's Todos</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
class="photo"
>
<ul>
<li>Invent new traffic lights
<li>Rehearse a movie scene
<li>Improve the spectrum technology
</ul>

これをコンポーネントの中に入れたいとします:

export default function TodoList() {
return (
// ???
)
}

そのままコピー・ペーストした場合、うまく動きません:

export default function TodoList() {
  return (
    // This doesn't quite work!
    <h1>Hedy Lamarr's Todos</h1>
    <img 
      src="https://i.imgur.com/yXOvdOSs.jpg" 
      alt="Hedy Lamarr" 
      class="photo"
    >
    <ul>
      <li>Invent new traffic lights
      <li>Rehearse a movie scene
      <li>Improve the spectrum technology
    </ul>

これは、JSX の方が厳密であり、HTML よりも若干ルールが多いからです。上記のエラーメッセージを読めばマークアップの修正方法は分かるようになっていますが、今は以下のガイドに従えば大丈夫です。

補足

大抵の場合は React が画面上に表示するエラーメッセージが、問題のある場所を見つける手がかりになります。困ったら読んでみてください!

JSX のルール

1. 単一のルート要素を返す

コンポーネントから複数の要素を返すには、それを単一の親タグで囲みます

例えば <div> を使うことができます。

<div>
<h1>Hedy Lamarr's Todos</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
class="photo"
>
<ul>
...
</ul>
</div>

マークアップに余分な <div> を加えたくない場合は、代わりに <></> を使うことができます。

<>
<h1>Hedy Lamarr's Todos</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
class="photo"
>
<ul>
...
</ul>
</>

この中身のないタグはフラグメント (Fragment) と呼ばれるものです。フラグメントを使えば、ブラウザの HTML ツリーに痕跡を残すことなく、複数の要素をまとめることができます。

さらに深く知る

JSX タグが複数あるときにラップしないといけない理由

JSX は HTML のように見えますが、裏ではプレーンな JavaScript オブジェクトに変換されます。関数から 2 つのオブジェクトを返したい場合、配列でラップしないといけませんよね。2 つの JSX タグを返したい場合に別のタグかフラグメントでラップしないといけないのも、同じ理由です。

2. すべてのタグを閉じる

JSX ではすべてのタグを明示的に閉じる必要があります。<img> のような自動で閉じるタグは <img /> のようになりますし、<li>oranges のような囲みタグは <li>oranges</li> と書かなければなりません。

Hedy Lamarr の画像とリスト項目は、閉じタグを書いた状態では以下のようになります。

<>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
class="photo"
/>
<ul>
<li>Invent new traffic lights</li>
<li>Rehearse a movie scene</li>
<li>Improve the spectrum technology</li>
</ul>
</>

3. (ほぼ)すべてキャメルケースで!

JSX は JavaScript に変換され、中に書かれた属性は JavaScript オブジェクトのキーになります。コンポーネント内では、これらの属性を変数に読み出したくなることがよくあります。しかし JavaScript の変数名には一定の制約があります。例えば、名前にハイフンを含めたり class のような予約語を使ったりすることはできません。

このため、React では多くの HTML および SVG の属性はキャメルケースで書かれます。例えば stroke-width の代わりに strokeWidth を使います。class は予約語なので、React では className を使います(対応する DOM プロパティが由来となっています)。

<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>

全リストは React DOM コンポーネントに存在する属性の一覧にあります。何かを間違ったとしても心配は要りません。ブラウザのコンソールにメッセージと修正の提案が表示されるようになっています。

落とし穴

歴史的理由により、aria-*data-* 属性は HTML 属性と同じようにハイフン付きで書くことになっています。

ヒント:JSX コンバータを使う

既存のマークアップの属性をすべて書きかえていくのは時に面倒です! 既存の HTML や SVG を JSX に変換する場合はコンバータを使うことをお勧めします。コンバータは実用上非常に役に立ちますが、自分でも楽に JSX が書けるよう、何が起こっているのかを理解しておくことも大切です。

最終結果は以下のようなものになります。

export default function TodoList() {
  return (
    <>
      <h1>Hedy Lamarr's Todos</h1>
      <img 
        src="https://i.imgur.com/yXOvdOSs.jpg" 
        alt="Hedy Lamarr" 
        className="photo" 
      />
      <ul>
        <li>Invent new traffic lights</li>
        <li>Rehearse a movie scene</li>
        <li>Improve the spectrum technology</li>
      </ul>
    </>
  );
}

まとめ

これで JSX が存在する理由と、コンポーネント内での使い方について理解しました。

  • レンダリングロジックとマークアップは互いに関連しているので、React ではそれらをグループ化する。
  • JSX は HTML と似ているがいくつかの違いがある。必要ならコンバータを使える。
  • エラーメッセージを見れば、大概はマークアップの修正方法について指針が得られる。

チャレンジ 1/1:
HTML を JSX に変換する

この HTML はコンポーネント内に貼り付けられたものですが、正しい JSX ではありません。修正してください。

export default function Bio() {
  return (
    <div class="intro">
      <h1>Welcome to my website!</h1>
    </div>
    <p class="summary">
      You can find my thoughts here.
      <br><br>
      <b>And <i>pictures</b></i> of scientists!
    </p>
  );
}

手作業で直すかコンバータを使うかはお任せします!