
HTMLからJSXへ:Reactビギナーがつまずく7つの落とし穴
📷 Pexels / PexelsHTMLからJSXへ:Reactビギナーがつまずく7つの落とし穴
className、htmlFor、自己閉鎖タグ、インラインスタイル — HTML-to-JSX変換は微妙なトラップだらけ。一度学べば二度とデバッグしなくて済む。
ほぼすべてのReact学習者に起こる通過儀礼があります。デザインのハンドオフ、静的テンプレート、または古いプロジェクトからコピーした良いHTMLのかたまりがあって、それをコンポーネントにそのままドロップします。するとエラーが始まります。
Warning: Invalid DOM property `class`. Did you mean `className`?
次に別のもの。
Warning: Invalid DOM property `for`. Did you mean `htmlFor`?
次におそらく閉じていない<img>や<input>タグからの解析エラー。6つの警告をスクロールし終えるころには、なぜReactが通常のHTMLを受け付けないのかと思っています。実はもっともな質問です。
なぜJSXはHTMLではないのか
簡単な答え:JSXはHTMLのように見えますが、変装したJavaScriptです。Babelがコンポーネントを処理するとき、すべての<div className="wrapper">はReact.createElement('div', { className: 'wrapper' })になります。その変換がJSXをとても強力にするものですが、HTMLをそのままコピーできない理由でもあります。
HTMLはマークアップ言語です。独自の解析ルールがあり、ブラウザはそれに対して寛容に設計されています。JSXはHTMLに似ているJavaScript構文です。JavaScriptパーサーは寛容ではありません。予約キーワード、厳格な構文ルール、曖昧さへの不寛容があります。
そのテンション — HTMLの緩やかさとJavaScriptの厳格さ — が、遭遇するすべての変換エラーの根本原因です。
知っておく必要がある変換
1. class → className
これは最初に学ぶもので、通常は警告から。
<!-- HTML -->
<div class="container main-content">
<p class="text-gray">Hello world</p>
</div>
// JSX
<div className="container main-content">
<p className="text-gray">Hello world</p>
</div>
理由は単純です:classはJavaScriptの予約キーワードです(ES6クラスを定義する方法)。JSXがclassを属性として許可すると、JavaScriptパーサーが混乱します。classNameは同名のDOMプロパティに直接マップする薄いワークアラウンドです — ReactがリアルDOMにレンダリングするとき、classNameプロパティを設定し、これはHTMLclass属性に対応します。最終結果は同一で、構文だけが変わります。
2. for → htmlFor
同じロジックです。forはJavaScriptの予約キーワードです(forループを書く方法)。フォームのラベルはforを使って入力のidに接続します:
<!-- HTML -->
<label for="email">Email address</label>
<input type="email" id="email" />
// JSX
<label htmlFor="email">Email address</label>
<input type="email" id="email" />
多くの開発者はclassNameを覚えますがhtmlForを忘れます。あまり議論されていませんが、見落とすと同じ種類の警告が出ます。
3. 自己閉鎖ボイド要素
HTMLでは、ボイド要素 — 子を持てないタグ — は閉じるスラッシュを必要としません。ブラウザはこれで問題ありません:
<!-- Valid HTML -->
<img src="photo.jpg" alt="A photo">
<input type="text" name="username">
<br>
<hr>
<meta charset="UTF-8">
JSXはすべての要素を明示的に閉じることを要求します。>の前に閉じるスラッシュを追加するか、別の閉じタグを追加します:
// JSX - 自己閉鎖が必要
<img src="photo.jpg" alt="A photo" />
<input type="text" name="username" />
<br />
<hr />
<meta charSet="UTF-8" />
最後の例でcharsetをcharSetに変更したことに注意してください。これはキャメルケースのパターンで、次に説明します。
4. キャメルケース属性
HTML属性は大文字と小文字を区別しません。JSX属性名はDOMプロパティにマップし、DOMプロパティはキャメルケースの規則に従います。よく遭遇するもの:
| HTML | JSX |
|---|---|
onclick | onClick |
onchange | onChange |
tabindex | tabIndex |
maxlength | maxLength |
readonly | readOnly |
autocomplete | autoComplete |
autofocus | autoFocus |
crossorigin | crossOrigin |
charset | charSet |
イベントハンドラはおそらく最もよく遭遇します。HTMLイベント属性はすべて小文字を使います:
<!-- HTML -->
<button onclick="handleClick()">Click me</button>
<input onchange="handleChange(event)" />
JSXではキャメルケースで、文字列ではなく関数参照を渡します:
// JSX
<button onClick={handleClick}>Click me</button>
<input onChange={handleChange} />
最後のポイント — 文字列の呼び出しではなく関数参照を渡す — は重要な違いです。HTMLonclickでは、値は実行するJavaScriptの文字列です。JSXonClickでは、値はJavaScript式(通常は関数参照)です。そのためJSXは引用符ではなく波括弧{}を使います。
5. JavaScriptオブジェクトとしてのインラインスタイル
これは初めての人を驚かせます。HTMLインラインスタイルは文字列です:
<!-- HTML -->
<div style="color: red; font-size: 16px; margin-top: 8px;">
Styled text
</div>
JSXインラインスタイルはJavaScriptオブジェクトです。つまり:
- 二重の波括弧(JSX式用に1組、オブジェクトリテラル用に1組)
- キャメルケースのプロパティ名
- 文字列または数値としての値(ピクセル値の数値はオプションで普通の数値でも可)
// JSX
<div style={{ color: 'red', fontSize: '16px', marginTop: '8px' }}>
Styled text
</div>
キャメルケースのルールはここでも適用されます:font-sizeはfontSizeに、margin-topはmarginTopに、background-colorはbackgroundColorになります。ハイフンでつながれたCSSプロパティはキャメルケースになります。
6. コメント
HTMLでは<!-- comment -->と書きます。JSXではHTMLスタイルのコメントは機能しません。JSX式内でJavaScriptコメントを使う必要があります:
<!-- HTML comment -->
<div>
<!-- This is a comment -->
<p>Content</p>
</div>
{/* JSX comment */}
<div>
{/* This is a comment */}
<p>Content</p>
</div>
//を単一行コメントにも使えますが、波括弧内か通常のJavaScriptコードの中だけで — JSXマークアップの直中ではありません。
あまり明白でない落とし穴
ブール属性の動作が異なる
HTMLでは、属性の存在がブール属性に対してtrueを意味します:
<!-- HTML - これらは同等 -->
<input disabled>
<input disabled="true">
<input disabled="disabled">
JSXでは、ブール属性は省略形で書けます(HTMLと同様)が、明示的な形式は異なって見えます:
// JSX - これらはすべて同等
<input disabled />
<input disabled={true} />
JSXで避けるべきはdisabled="true"(引用符付き)で、ブール値のtrueではなく文字列"true"を渡すからです。Reactは正しくレンダリングするかもしれませんが、慣用的ではなく、一部のコンポーネントで問題を起こす可能性があります。
リスト内のkeyプロップ
HTMLリストをJSXに変換してデータをマップする場合、Reactは各要素にkeyプロップを必要とします。これは厳密にはHTML-to-JSX変換の問題ではありません — 要素を動的に生成する場合にのみ適用されます — しかし警告に当たる前に知っておく価値があります:
// keyがないとReactは警告
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
SVG属性
SVGには独自の属性命名の癖があります。HTML/SVGではfill-opacity、stroke-width、clip-pathと書くかもしれません。JSXではこれらはfillOpacity、strokeWidth、clipPathになります。アイコンライブラリやデザインツールからインラインSVGを貼り付ける場合、いくらかのクリーンアップが必要です。
tabindexとtabIndex
見逃しやすい。アクセシブルなコンポーネントを構築してtabindexを手動で設定する場合、JSXではtabIndexになることを覚えておいてください。これを見逃してもエラーが発生しない場合があります — Reactはレンダリングするかもしれません — しかし動作に影響する可能性のある静かな違いです。
自動変換ツールを使う場合
HTMLの大きなブロック — デザインシステムからのコンポーネント、非Reactコードベースから移行しているテンプレート、またはオンラインソースからのスニペット — がある場合、変換ツールを使うのが実用的な選択です。すべてのclassをclassNameに手動で置き換えて、すべてのハイフンでつながれたイベントハンドラを置き換えるのは面倒でエラーが起きやすいです。
ToolPal HTML to JSXコンバーターは標準的な変換を自動的に処理します:class → className、for → htmlFor、スタイル文字列 → スタイルオブジェクト、自己閉鎖ボイド要素、キャメルケース属性。HTMLを貼り付けると有効なJSXが返ってきて、次に進めます。
自動変換ツールが助けられないのは構造的な決定です。500行のHTMLを単一のJSXリターン文に変換しても、うまく設計されたコンポーネントは得られません — 変換された塊が得られるだけです。実質的なものには、コンバーターは構文を処理しますが、結果をどのように小さなコンポーネントに分割するか、どこでプロップがハードコードされた値を置き換えるべきか、どの部分を動的にすべきかを決定するのは依然としてあなたです。
変換ツールは機械的な変換に使います。アーキテクチャには自分の頭を使います。
ゼロからJSXを書く場合
新しいコンポーネントを構築していてデザインが頭の中(またはモックアップ)にある場合、JSXを直接書く方が変換するよりも速いことがよくあります。最初からclassNameを書き、ボイド要素を閉じることを忘れず、変換が必要なスタイル文字列を作らないで済みます。
変換が本当に役立つ場合:
非Reactプロジェクトからの移行。 既存のHTMLテンプレートがある場合 — プレーンHTML/CSSサイト、Railsアプリ、Jinjaテンプレートから — 変換ツールが本物の時間を節約します。
デザインハンドオフからの作業。 デザイナーはよくHTMLマークアップをエクスポートしたりスニペットを提供したりします。それを変換ツールに通すのは再入力するより速い。
サードパーティスニペットの貼り付け。 ドキュメント、Stack Overflowの回答、またはチュートリアルからのコード例はしばしばHTMLで書かれています。クイック変換で手動編集なしに使えるJSXが得られます。
大きなフォーム。 何十ものインプット、ラベル、フィールドセットを持つフォームは手動で書くのが面倒です。HTML構造を貼り付け、変換し、ハードコードされた値をプロップとステートに置き換えます。
現実的な変換例
HTMLの小さなカードコンポーネント:
<div class="card" id="product-card">
<img src="/product.jpg" alt="Product photo" class="card-image">
<div class="card-body">
<h2 class="card-title">Product Name</h2>
<p class="card-description" style="color: #666; font-size: 14px;">
A short description of the product.
</p>
<label for="quantity">Quantity</label>
<input type="number" id="quantity" min="1" max="99" readonly>
<button class="btn btn-primary" onclick="addToCart()">Add to Cart</button>
</div>
</div>
変換後:
<div className="card" id="product-card">
<img src="/product.jpg" alt="Product photo" className="card-image" />
<div className="card-body">
<h2 className="card-title">Product Name</h2>
<p className="card-description" style={{ color: '#666', fontSize: '14px' }}>
A short description of the product.
</p>
<label htmlFor="quantity">Quantity</label>
<input type="number" id="quantity" min="1" max="99" readOnly />
<button className="btn btn-primary" onClick={addToCart}>Add to Cart</button>
</div>
</div>
すべての変更に注意:
- すべての
class→className <img>と<input>は/>で自己閉鎖style文字列 → キャメルケースプロパティを持つスタイルオブジェクトfor→htmlForreadonly→readOnlyonclick="addToCart()"→onClick={addToCart}(関数参照、括弧なし)
イベントハンドラに関する最後のポイントは強調する価値があります。HTML版では、onclick="addToCart()"は評価される文字列です。JSXで誤ってonClick="addToCart()"と書くと、Reactはイベントハンドラが文字列ではなく関数である必要があるというエラーをスローします。そしてonClick={addToCart()}(括弧付き)と書くと、クリック時ではなくレンダリング時に関数がすぐに呼び出されます。どちらも一般的な初心者のミスです。
変換ツールが処理できないいくつかのこと
動的コンテンツ。 変換ツールはProduct Nameが{product.name}になるべきと知りません。構造は変換しますが、静的な値をプロップとステートに置き換えるのはあなた自身が行う必要があります。
イベントハンドラのロジック。 onclick="addToCart()"はonClick={addToCart}に変換されますが、addToCartはまだコンポーネント内のどこかに定義される必要があります。変換ツールは属性構文を提供します;関数はあなたの責任です。
条件付きレンダリング。 HTMLには「Xが真の場合にのみこの要素を表示する」という概念がありません。そのパターンはコンポーネント化のステップで追加するもので、変換ツールが静的マークアップから推測できるものではありません。
複数のルート要素。 HTMLスニペットのトップレベルに2つの兄弟要素がある場合、変換されたJSXにも2つの兄弟要素があります — これはコンポーネントのリターン文では無効です。<div>または<>...</>フラグメントでラップする必要があります。
HTML-to-JSX変換は、慣れるまで面倒に感じる類のものです。Reactを数週間書いた後は、自動的に考えずにclassNameを入力するようになります。反射的に<img />タグを閉じるようになります。しかしその筋肉記憶が発達するまで、ToolPal HTML to JSXコンバーターのようなツールが移行をより速く、より少ないフラストレーションで進めます。
本当のスキルは構文の違いを暗記することではありません — それが存在する理由を理解することです。JSXがJavaScriptであることを理解すれば、ルールが恣意的に感じられなくなり、意味をなすようになります。