Reactの基礎 - JSXの基本構文
概要
JSX (JavaScript XML) は、JavaScript内でHTMLライクなマークアップを記述するためのシンタックス拡張である。
ReactはJSXを用いることで、UIの構造をJavaScriptコードと同じファイル内に直感的に表現できる。
JSXはWebブラウザがそのまま解釈できるわけではなく、BabelやTypeScriptコンパイラ等のトランスパイラによって、JavaScriptの関数呼び出しに変換される。
変換後のコードは最終的にDOMを操作するReactの関数呼び出しとなる。
主な特徴は以下の通りである。
- JavaScript内にHTMLライクなマークアップを記述できる。
- 波括弧
{}を使用してJavaScript式を埋め込める。 - HTML属性名とは異なるcamelCaseベースの属性名を使用する。
- React 17以降では
import React from 'react'の記述が不要になった。 - TypeScriptと組み合わせる場合は、.tsx拡張子 のファイルを使用する。
JSXとは
JSXはJavaScriptのシンタックス拡張であり、JavaScript内でHTMLライクなマークアップを記述することを可能にする。
JSX自体はJavaScriptの仕様ではなく、トランスパイラによる変換を前提とした構文である。
JSXの変換の仕組み
React 16以前では、JSXはBabelによって React.createElement() 関数の呼び出しに変換されていた。
そのため、JSXを使用する全てのファイルで import React from 'react' の記述が必須だった。
JSXと変換後のコードの対応例を以下に示す。
// JSXによる記述
const element = <h1 className="greeting">Hello, World!</h1>;
// Babelによる変換後 (React 16以前)
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, World!'
);
React.createElement() は第1引数にタグ名または関数コンポーネント、第2引数に属性のオブジェクト、第3引数以降に子要素を受け取る。
子要素が複数ある場合は、以下のように第3引数以降に続けて渡される。
// JSXによる記述
const element = (
<div className="container">
<h1>Title</h1>
<p>Description</p>
</div>
);
// Babelによる変換後 (React 16以前)
const element = React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Title'),
React.createElement('p', null, 'Description')
);
React 17以降の新しいJSXトランスフォーム
React 17から、新しいJSXトランスフォームが導入された。
この変換では、React.createElement() の代わりに react/jsx-runtime から _jsx 関数が自動的にインポートされる。
新しいJSXトランスフォームによる変換例を以下に示す。
// JSXによる記述 (import Reactの記述が不要)
function App() {
return <h1>Hello, World!</h1>;
}
// コンパイラによる変換後 (React 17以降)
import { jsx as _jsx } from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello, World!' });
}
下表に、新しいJSXトランスフォームの変更点を示す。
| 変更点 |
|---|
import React from 'react' の記述が不要になった。
|
JSXファイルに React 変数が存在しなくてもコンパイルエラーが発生しない。
|
react/jsx-runtime からの自動インポートはバンドルツールが処理する。
|
JSXにおける式の埋め込み
JSXでは、波括弧 {} を使用してJavaScript式を埋め込むことができる。
これは、テキスト部分と属性値の両方で使用できる。
波括弧 {} の基本
波括弧を使用できる場所は2箇所ある。
| 場所 | 例 |
|---|---|
| タグ内のテキスト部分 | {expression} |
| 属性値 | <img src={imageUrl} /> |
変数、計算式、関数呼び出しの埋め込み例を以下に示す。
const user = {
name: 'Alice',
age: 30,
imageUrl: 'https://example.com/alice.jpg'
};
function formatGreeting(name) {
return `Hello, ${name}!`;
}
function UserCard() {
return (
<div>
{/* 変数の埋め込み */}
<h1>{user.name}</h1>
{/* 計算式の埋め込み */}
<p>来年の年齢: {user.age + 1}</p>
{/* 関数呼び出しの埋め込み */}
<p>{formatGreeting(user.name)}</p>
{/* 属性値への埋め込み */}
<img src={user.imageUrl} alt={user.name} />
</div>
);
}
埋め込み可能な式と不可能な文
波括弧内に記述できるのはJavaScriptの 式 (expression) に限られる。
値を返さない 文 (statement) は直接埋め込むことができない。
下表に、埋め込み可能な式と埋め込み不可能な文の例を示す。
| 分類 | 例 | 備考 |
|---|---|---|
| 変数参照 (式) | {name} | 変数の値が表示される。 |
| 文字列連結 (式) | {'Hello, ' + name} | 連結した文字列が表示される。 |
| 算術演算 (式) | {price * quantity} | 計算結果が表示される。 |
| 関数呼び出し (式) | {formatDate(date)} | 戻り値が表示される。 |
| 三項演算子 (式) | {isLoggedIn ? 'ログイン中' : 'ログアウト'} | 条件による切り替え |
| 論理演算 (式) | {count > 0 && {count}} | 短絡評価による条件付き表示 |
| プロパティアクセス (式) | {user.name} | オブジェクトのプロパティ参照 |
| if文 (文) | {if (条件) { ... }} | 埋め込み不可。三項演算子または論理演算を使用する |
| for文 (文) | {for (let i ...) { ... }} | 埋め込み不可 Array.map() を使用する。 |
| while文 (文) | {while (条件) { ... }} | 埋め込み不可 Array.map() 等を使用する。 |
| 変数宣言 (文) | {const x = 1} | 埋め込み不可 JSX外で宣言する。 |
| オブジェクト直接参照 (不可) | {person} | エラー {person.name} のようにプロパティを指定する。 |
JSXの属性
JSXの属性は、HTMLの属性と似た構文で記述するが、いくつかの重要な違いがある。
HTML属性との違い
JSXの属性名は基本的に キャメルケース (camelCase) を使用する。
ただし、ARIA属性 (aria-*) と data属性 (data-*) はハイフン区切りをそのまま使用する。
下表に、HTMLとJSXの属性名の対応を示す。
| HTML属性 | JSX属性 | 備考 |
|---|---|---|
class |
className |
JavaScriptの予約語 class との衝突を避けるため
|
for |
htmlFor |
JavaScriptの予約語 for との衝突を避けるため
|
tabindex |
tabIndex |
camelCaseに変換 |
contenteditable |
contentEditable |
camelCaseに変換 |
stroke-width |
strokeWidth |
SVG属性もcamelCaseに変換 |
aria-label |
aria-label |
ARIA属性はハイフン区切りを保持 |
data-testid |
data-testid |
data属性はハイフン区切りを保持 |
属性値の指定方法
JSXの属性値には、文字列リテラル、JavaScript式、スプレッド構文の3つの指定方法がある。
const attrs = { src: avatarUrl, alt: userName, width: 100 };
function Avatar() {
return (
<div>
{/* 文字列リテラル: クォートで囲む */}
<img className="avatar" alt="ユーザアバター" />
{/* JavaScript式: 波括弧で囲む */}
<img src={avatarUrl} alt={userName} />
{/* スプレッド構文: オブジェクトの全プロパティを展開 */}
<img {...attrs} />
</div>
);
}
属性値の指定に関する注意点を以下に示す。
- src="{avatarUrl}" のようにクォート内で波括弧を使用すると、式ではなく文字列として渡される。
- 文字列と式を同時に指定する場合は、テンプレートリテラルを式として渡す。
- className={`button ${isActive ? 'active' : }`}
スタイルの指定
JSXでは style 属性にJavaScriptオブジェクトを渡す。
HTMLのように文字列で指定することはできない。
function StyledComponent() {
// スタイルオブジェクトを変数として定義する方法
const containerStyle = {
backgroundColor: 'black',
color: 'pink',
padding: 20, // 数値はデフォルトで px 単位になる
fontSize: '1rem' // 文字列で単位を指定することもできる
};
return (
<div>
{/* 変数を使用する方法 */}
<div style={containerStyle}>コンテンツ</div>
{/* インラインで直接指定する方法 (二重波括弧) */}
{/* 外側の波括弧: JSX式、内側の波括弧: JavaScriptオブジェクト */}
<p style={{ marginTop: 10, fontWeight: 'bold' }}>テキスト</p>
</div>
);
}
スタイル指定に関する重要な規則を以下に示す。
- CSSプロパティ名は、キャメルケース (camelCase) で記述する。
- background-color -> backgroundColor
- 数値を指定した場合、デフォルトで
px単位が付与される- marginTop: 20 -> margin-top: 20px
px以外の単位を使用する場合は文字列で指定する。- width: '50%'
JSXのルール
JSXを正しく使用するために、いくつかの重要なルールがある。
単一ルート要素
JSXのコンポーネントから返す要素は、必ず1つのルート要素でラップする必要がある。
この制約はJSXがJavaScriptオブジェクトに変換されることに起因する。
1つの関数が複数の値を返せないのと同様に、JSXも1つのルート要素しか返せない。
// エラー : 複数のルート要素
function BadComponent() {
return (
<h1>タイトル</h1>
<p>説明文</p>
);
}
// 解決策1 : divでラップする
function GoodComponent1() {
return (
<div>
<h1>タイトル</h1>
<p>説明文</p>
</div>
);
}
// 解決策2 : フラグメントでラップする (DOMに余分なノードが追加されない)
function GoodComponent2() {
return (
<>
<h1>タイトル</h1>
<p>説明文</p>
</>
);
}
全てのタグは閉じる必要がある
JSXでは、全てのHTMLタグを閉じる必要がある。
HTMLでは省略可能な自己閉じタグも、JSXでは明示的に閉じる必要がある。
function FormExample() {
return (
<div>
{/* 自己閉じタグは必ずスラッシュを付ける */}
<img src="photo.jpg" alt="写真" />
<br />
<hr />
<input type="text" />
{/* コンテンツを持つタグは閉じタグが必要 */}
<li>リスト項目</li>
<p>段落テキスト</p>
</div>
);
}
フラグメント
フラグメントは、DOMに余分なノードを追加せずに複数の要素をグループ化するための仕組みである。
React.Fragmentの基本
フラグメントには短縮構文 <>...</> と 明示的な <React.Fragment>...</React.Fragment> の2つの記法がある。
import React from 'react';
function TableRows() {
return (
<>
{/* 短縮構文: DOMにノードが追加されない */}
<tr>
<td>行1のデータ1</td>
<td>行1のデータ2</td>
</tr>
<tr>
<td>行2のデータ1</td>
<td>行2のデータ2</td>
</tr>
</>
);
}
function App() {
return (
<table>
<tbody>
<TableRows />
</tbody>
</table>
);
}
フラグメントを使用するメリットを以下に示す。
- 不要な
div要素の追加を避けられる。 - テーブルやフレックスボックスのレイアウトを壊さない。
- DOMのネスト構造をシンプルに保てる。
keyを持つフラグメント
リストのレンダリング時等、フラグメントに key 属性を指定する必要がある場合は、短縮構文 <> を使用できない。
この場合は React.Fragment を明示的にインポートして使用する。
import { Fragment } from 'react';
const items = [
{ id: 1, title: '項目1', description: '説明1' },
{ id: 2, title: '項目2', description: '説明2' },
{ id: 3, title: '項目3', description: '説明3' },
];
function ItemList() {
return (
<dl>
{items.map((item) => (
// key属性を持つフラグメントには React.Fragment を使用する
<Fragment key={item.id}>
<dt>{item.title}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
);
}
key 属性を持つフラグメントに関する注意点を以下に示す。
- 短縮構文
<></>はkey属性を受け付けない。 Fragmentはreactパッケージからインポートする必要がある- import { Fragment } from 'react'
key以外の属性はフラグメントに渡すことができない。
JSX と TypeScript (TSX)
TypeScriptでReactを使用する場合、ファイルの拡張子を .tsx拡張子 とすることでJSXが使用できる。
ただし、ジェネリクス (型パラメータ) と JSXの構文が衝突する問題に注意が必要である。
型パラメータとJSXの衝突
アロー関数でジェネリクスを使用する場合、型パラメータの <T> がJSXのタグとして解釈されてしまう問題が発生する。
// エラー : <T> がJSXタグとして解釈される (.tsx ファイルの場合)
const identity = <T>(value: T): T => value;
// 解決策1 (推奨) : function宣言を使用する
function identity<T>(value: T): T {
return value;
}
// 解決策2 : 末尾カンマを追加する (TypeScriptコンパイラへのヒント)
const identity = <T,>(value: T): T => value;
// 解決策3 : extends を使用して型パラメータを制約する
const identity = <T extends unknown>(value: T): T => value;
各解決策の特徴を以下に示す。
- function宣言を使用する方法が最も明確でTypeScriptの慣習に沿っており、推奨される。
- 末尾カンマ
<T,>はTypeScriptコンパイラに型パラメータであることを示すが、ソースコードの可読性が落ちる場合がある。 extendsを使用する方法は型制約を意図していると誤解される可能性がある。
関連情報
- Reactの基礎 - TSXの基本構文
- Reactの基礎 - 条件レンダリング
- Reactの基礎 - リストレンダリング
- Reactの基礎 - コンポーネント
- Reactの基礎 - PropsとChildren