デザインについての学習メモブログ

React入門 #04 – JSXの書き方と基本ルール ~直感的なUI記述をマスターする~

記事内に広告が含まれています。

React入門 #04 – JSXの書き方と基本ルール ~直感的なUI記述をマスターする~

JSX(JavaScript XML)は、Reactの最も特徴的な機能の一つです。

JavaScriptの中にHTML風の構文を書けるため、UIを直感的に記述できます。

この記事では、JSXの基本ルールから実践的な使い方まで、詳しく解説していきます。

JSXとは?

JSXの基本概念

JSXは、JavaScriptの構文拡張です。

HTMLに似た記法でUIを記述できます。

JavaScript版:

JSX
const element = <h1>こんにちは、React!</h1>

TypeScript版:

TSX
const element: JSX.Element = <h1>こんにちは、React!</h1>

📝 JSX.Elementについて

JSX.Elementは、TypeScriptでJSX式の型を表す型定義です。つまり、TypescriptがJSXとして正しい構造になっているかを検証してくれるようになります。

型に一致する例:

TypeScript
const element1: JSX.Element = <div>Hello</div> 
const element2: JSX.Element = <div><h1>Title</h1><p>Text</p></div> 
const element3: JSX.Element = <>
                                <div>A</div>
                                <div>B</div>
                              </>

JSXは実際には何に変換される?

JSXはブラウザでは理解できないコードのため、Viteなどのビルドツールが通常のJavaScriptに変換します。

JSXコード:

JSX
const element = <h1>こんにちは</h1>

変換後のJavaScript(内部的に):

JavaScript
const element = React.createElement('h1', null, 'こんにちは')

つまり、JSXは「見やすく書くための糖衣構文(シンタックスシュガー)」なのです。

HTMLに近い書き方で要素の階層構造が読み取りやすくなります。

なぜJSXを使うのか?

JSXを使う最大の理由は、直感的なコード記述と型安全性の両立です。

TypeScriptと組み合わせることで、JSXは`JSX.Element`型として扱われ、コンパイル時に構文の正しさが自動的にチェックされます。

これにより、実行前にエラーを発見でき、より安全な開発が可能になります。

メリット:

  • HTMLライクで直感的
  • JavaScriptの強力な機能が使える
  • コンポーネントの構造が分かりやすい
  • エディタのサポート(補完、エラー検出)が優れている

JSXの基本ルール

JSXの書き方ルールは以下の6つになります。

  • 必ず一つの親要素で囲む
  • すべてのタグを閉じる
  • classNameを使う
  • キャメルケースを使う
  • styleはオブジェクトで指定
  • コメントの書き方

順番に具体例を交えて解説します。

ルール1: 必ず一つの親要素で囲む

❌ 悪い例:

returnに2つの要素を渡しています。

JSX
function App() {
  return (
    <h1>タイトル</h1>
    <p>本文</p>
  )
}
// エラー: Adjacent JSX elements must be wrapped in an enclosing tag

✅ 良い例1: divで囲む

JSX
function App() {
  return (
    <div>
      <h1>タイトル</h1>
      <p>本文</p>
    </div>
  )
}

✅ 良い例2: Fragmentを使う(推奨)

JSX
function App() {
  return (
    <>
      <h1>タイトル</h1>
      <p>本文</p>
    </>
  )
}

// または明示的に書く
function App() {
  return (
    <React.Fragment>
      <h1>タイトル</h1>
      <p>本文</p>
    </React.Fragment>
  )
}

📝 Fragmentとは?

  • 余分なDOM要素を作らずにグループ化できる
  • <></><React.Fragment></React.Fragment>短縮形
  • CSSのレイアウトに影響を与えない

Fragmentにkeyを付ける場合:

JSX
function List() {
  const items = [
    { id: 1, text: 'Item 1' },
    { id: 2, text: 'Item 2' }
  ]
  
  return (
    <>
      {items.map(item => (
        <React.Fragment key={item.id}>
          <h3>{item.text}</h3>
          <p>説明文</p>
        </React.Fragment>
      ))}
    </>
  )
}

keyはこの記事の下部であらためて解説します。

現時点では、ループ処理で繰り返し同じ要素が作成される時に個々の要素を区別する識別子と考えてください。

ルール2: すべてのタグを閉じる

HTMLでは省略可能な閉じタグも、JSXでは必須です。

❌ 悪い例:

JSX
<input type="text">
<img src="image.jpg">
<br>

✅ 良い例:

JSX
<input type="text" />
<img src="image.jpg" alt="説明" />
<br />

自己閉じタグ(Self-closing tag):

  • 子要素を持たない要素は/>で閉じる
  • <img />, <input />, <br />, <hr />など

ルール3: classNameを使う

HTMLのclass属性は、JSXではclassNameになります。

👉 理由:

HTMLのように見えても実態はJacascriptです。JavaScriptではclassは予約語(クラス定義に使う)のため、別の目的のワードとして使えません。

❌ 悪い例:

JSX
<div class="container">コンテンツ</div>

✅ 良い例:

JSX
<div className="container">コンテンツ</div>

複数のクラスを指定:

JSX
<div className="container main-content">コンテンツ</div>

// 動的に組み合わせる
const isActive = true
<div className={`container ${isActive ? 'active' : ''}`}>
  コンテンツ
</div>

ルール4: キャメルケースを使う

HTML属性イベントハンドラは、キャメルケース(camelCase)で書きます。

HTML属性:

JSX
// ❌ HTML
<label for="name">名前</label>

// ✅ JSX
<label htmlFor="name">名前</label>

イベントハンドラ:

JSX
// ❌ HTML
<button onclick="handleClick()">クリック</button>

// ✅ JSX
<button onClick={handleClick}>クリック</button>

よく使う変換例:

  • onclickonClick
  • onchangeonChange
  • onsubmitonSubmit
  • tabindextabIndex
  • readonlyreadOnly

ルール5: styleはオブジェクトで指定

インラインスタイルは、文字列ではなくオブジェクトで指定します。

❌ 悪い例:

JSX
<div style="color: red; font-size: 20px;">テキスト</div>

✅ 良い例:

JSX
<div style={{ color: 'red', fontSize: '20px' }}>テキスト</div>

詳細:

JSX
// スタイルオブジェクトを変数に
const divStyle = {
  color: 'blue',
  backgroundColor: 'lightgray',
  padding: '10px',
  borderRadius: '5px'
}

function App() {
  return <div style={divStyle}>スタイル付きテキスト</div>
}

⚠️ 注意点:

  • CSSプロパティ名はキャメルケース(background-colorbackgroundColor
  • 値は文字列で囲む(数値の場合はpxが自動付与される)
  • {{二重括弧は、外側が「JavaScriptの埋め込み」、内側が「オブジェクトリテラル」

ルール6: コメントの書き方

JSXの中でコメントを書く場合は、波括弧で囲みます。

✅ 正しいコメント:

JSX
function App() {
  return (
    <div>
      {/* これはJSX内のコメントです */}
      <h1>タイトル</h1>
      
      {/* 
        複数行の
        コメントも書けます
        */}
      <p>本文</p>
    </div>
  )
}

// JSXの外(通常のJavaScript部分)ではこのコメント

❌ 間違ったコメント:

JSX
<div>
  // これは表示されてしまう
  <!-- これはエラーになる -->
</div>

JavaScriptを埋め込む

基本的な埋め込み

波括弧{}を使ってJavaScriptのを埋め込めます。

JavaScript版:

JSX
function App() {
  const name = '太郎'
  const age = 25
  const isStudent = true

  return (
    <div>
      <h1>こんにちは、{name}さん!</h1>
      <p>年齢: {age}</p>
      <p>学生: {isStudent ? 'はい' : 'いいえ'}</p>
      <p>来年は{age + 1}歳です</p>
    </div>
  )
}

10行目のように3項演算子なども埋め込めます。

TypeScript版:

TSX
function App(): JSX.Element {
  const name: string = '太郎'
  const age: number = 25
  const isStudent: boolean = true

  return (
    <div>
      <h1>こんにちは、{name}さん!</h1>
      <p>年齢: {age}</p>
      <p>学生: {isStudent ? 'はい' : 'いいえ'}</p>
      <p>来年は{age + 1}歳です</p>
    </div>
  )
}

式と文の違い

✅ 埋め込める(式):

JSX
{name}                    // 変数
{1 + 2}                   // 算術演算
{age > 20}                // 比較演算
{isActive ? 'ON' : 'OFF'} // 三項演算子
{items.map(...)}          // 配列メソッド
{getMessage()}            // 関数呼び出し

❌ 埋め込めない(文):

JSX
{if (condition) { ... }}  // if文
{for (let i = 0; ...) {}} // for文
{const x = 5}             // 変数宣言

回避方法:

JSX
// if文の代わりに三項演算子
{condition ? <div>True</div> : <div>False</div>}

// または"JSX外"で処理
function App() {
  let content
  if (condition) {
    content = <div>True</div>
  } else {
    content = <div>False</div>
  }
  
  return <div>{content}</div>
}

13行目の返り値として渡すElementの中では、”文”を入れない。

オブジェクトと配列

以下のサンプルはオブジェクトや配列をJSXオブジェクトに埋め込む記入例になります。

returnの後の部分に着目してください。

オブジェクトの利用:

JSX
function UserProfile() {
  const user = {
    name: '山田太郎',
    age: 30,
    email: 'yamada@example.com'
  }

  return (
    <div>
      <h2>{user.name}</h2>
      <p>年齢: {user.age}</p>
      <p>メール: {user.email}</p>
    </div>
  )
}

配列のマッピング:

JSX
function FruitList() {
  const fruits = ['りんご', 'バナナ', 'オレンジ']

  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
      ))}
    </ul>
  )
}

条件付きレンダリング

条件付きレンダリングは、特定の条件に応じてUIの表示・非表示を切り替える手法です。

Reactでは、JavaScriptの条件演算子(三項演算子や論理AND演算子)を使って、動的にコンテンツを出し分けることができます。

ログイン状態によるメッセージの変更や、データの読み込み状況に応じた表示切り替えなど、インタラクティブなアプリケーションには欠かせない機能です。

三項演算子

基本形:

JSX
function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? (
        <h1>おかえりなさい!</h1>
      ) : (
        <h1>ログインしてください</h1>
      )}
    </div>
  )
}

TypeScript版:

TSX
interface GreetingProps {
  isLoggedIn: boolean
}

function Greeting({ isLoggedIn }: GreetingProps): JSX.Element {
  return (
    <div>
      {isLoggedIn ? (
        <h1>おかえりなさい!</h1>
      ) : (
        <h1>ログインしてください</h1>
      )}
    </div>
  )
}

論理AND演算子(&&)

条件が真の場合だけ表示したい時に便利です。

JavaScript版:

JSX
function Notification({ hasNewMessage }) {
  return (
    <div>
      <h1>ホーム</h1>
      {hasNewMessage && (
        <div className="notification">
          新しいメッセージがあります
        </div>
      )}
    </div>
  )
}

⚠️ 注意点:

&&演算子は、左辺がfalsy(偽とみなされる値)の場合、その値自体を返します。

JavaScriptでは`0`や`””`(空文字列)、`NaN`はfalsyですが、ReactはこれらをDOMに描画してしまいます。

特に数値の`0`は画面に表示されてしまうため、意図しない表示になることがあります。

JSX
// ❌ 数値0は表示されてしまう
{count && <div>Count: {count}</div>}
// countが0の場合、画面に「0」が表示される

// ✅ 明示的に真偽値に変換
{count > 0 && <div>Count: {count}</div>}
{Boolean(count) && <div>Count: {count}</div>}

複数条件の分岐

3つ以上の条件で表示を切り替える場合、三項演算子をネストすると可読性が低下します。

そのような場合は、関数に切り出したり、オブジェクトマッピングを使うことで、よりシンプルで保守しやすいコードになります。

方法1: 関数に切り出す

条件分岐をコンポーネントの関数として定義する方法です。

if文を使った馴染みのある書き方で、複雑な条件ロジックも読みやすく記述できます。

JSX
function Status({ status }) {
  const getStatusMessage = () => {
    if (status === 'loading') return <p>読み込み中...</p>
    if (status === 'error') return <p>エラーが発生しました</p>
    if (status === 'success') return <p>完了しました!</p>
    return <p>待機中</p>
  }

  return <div>{getStatusMessage()}</div>
}

方法2: オブジェクトマッピング

状態表示内容をオブジェクトで対応付ける方法です。

条件が固定的で、単純な値の切り替えの場合に特に有効です。

TypeScriptと組み合わせると、型安全性も確保できます。

JSX
function Status({ status }) {
  const messages = {
    loading: <p>読み込み中...</p>,
    error: <p>エラーが発生しました</p>,
    success: <p>完了しました!</p>,
    idle: <p>待機中</p>
  }

  return <div>{messages[status] || messages.idle}</div>
}

TypeScript版:

TSX
type StatusType = 'loading' | 'error' | 'success' | 'idle'

interface StatusProps {
  status: StatusType
}

function Status({ status }: StatusProps): JSX.Element {
  const messages: Record<StatusType, JSX.Element> = {
    loading: <p>読み込み中...</p>,
    error: <p>エラーが発生しました</p>,
    success: <p>完了しました!</p>,
    idle: <p>待機中</p>
  }

  return <div>{messages[status]}</div>
}

5. リスト表示とkey

配列データを画面に表示する際、Reactでは`map()`メソッドを使って各要素をJSX要素に変換します。

このとき、各要素に一意の`key`属性を指定することが重要です。

keyはReactが要素を識別し、効率的にDOM更新を行うために必要で、これを適切に設定しないとパフォーマンスの問題やバグの原因となります。

基本的なリスト表示

JavaScript版:

JSX
function TodoList() {
  const todos = [
    { id: 1, text: '買い物に行く', completed: false },
    { id: 2, text: '洗濯をする', completed: true },
    { id: 3, text: '本を読む', completed: false }
  ]

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text} {todo.completed && '✓'}
        </li>
      ))}
    </ul>
  )
}

TypeScript版:

TSX
interface Todo {
  id: number
  text: string
  completed: boolean
}

function TodoList(): JSX.Element {
  const todos: Todo[] = [
    { id: 1, text: '買い物に行く', completed: false },
    { id: 2, text: '洗濯をする', completed: true },
    { id: 3, text: '本を読む', completed: false }
  ]

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text} {todo.completed && '✓'}
        </li>
      ))}
    </ul>
  )
}

keyの重要性

keyは、Reactが要素を識別するために使います。

❌ 悪い例:

JSX
// keyがない
{items.map(item => <li>{item}</li>)}

// indexをkeyにする(非推奨)
{items.map((item, index) => <li key={index}>{item}</li>)}

✅ 良い例:

JSX
// 一意のIDをkeyにする
{items.map(item => <li key={item.id}>{item.text}</li>)}

なぜindexをkeyにすると問題?

リストの順序が変わったり、要素が追加・削除されると、Reactが要素を正しく追跡できなくなります。

JSX
// ❌ 問題が起きる例
function BadList() {
  const [items, setItems] = useState(['A', 'B', 'C'])
  
  const addItem = () => {
    setItems(['NEW', ...items]) // 先頭に追加
  }
  
  return (
    <div>
      <button onClick={addItem}>追加</button>
      <ul>
        {items.map((item, index) => (
          <li key={index}>
            {item}
            <input type="text" />
          </li>
        ))}
      </ul>
    </div>
  )
}
// inputの値が意図せずずれてしまう

// indexをkeyにすると:
// 先頭に追加前:key 0=A, key 1=B, key 2=C
// 先頭に追加後:key 0=NEW, key 1=A, key 2=B, key 3=C(Cは消える)
// inputの値が意図せずずれてしまう

複雑なリスト表示

フィルタリングとソート:

JSX
function ProductList() {
  const products = [
    { id: 1, name: 'ノートPC', price: 100000, category: '電子機器' },
    { id: 2, name: 'マウス', price: 3000, category: '電子機器' },
    { id: 3, name: 'ノート', price: 500, category: '文房具' }
  ]

  const [category, setCategory] = useState('全て')

  const filteredProducts = category === '全て'
    ? products
    : products.filter(p => p.category === category)

  const sortedProducts = [...filteredProducts].sort((a, b) => a.price - b.price)

  return (
    <div>
      <select value={category} onChange={e => setCategory(e.target.value)}>
        <option>全て</option>
        <option>電子機器</option>
        <option>文房具</option>
      </select>

      <ul>
        {sortedProducts.map(product => (
          <li key={product.id}>
            {product.name} - ¥{product.price.toLocaleString()}
          </li>
        ))}
      </ul>
    </div>
  )
}

イベント処理

Reactでは、ユーザーの操作(クリック、入力、マウス移動など)に反応するために、イベントハンドラを使います。

HTMLのイベント属性と似ていますが、JSXではキャメルケースで記述し、関数を直接渡す点が異なります

適切なイベント処理を実装することで、ボタンのクリック、フォームの送信、キーボード操作など、あらゆるユーザーインタラクションに対応できます。

基本的なイベントハンドラ

JavaScript版:

JSX
function Button() {
  const handleClick = () => {
    alert('クリックされました!')
  }

  return <button onClick={handleClick}>クリック</button>
}

// インラインで書く場合
function Button() {
  return (
    <button onClick={() => alert('クリック!')}>
      クリック
    </button>
  )
}

TypeScript版:

TSX
function Button(): JSX.Element {
  const handleClick = (): void => {
    alert('クリックされました!')
  }

  return <button onClick={handleClick}>クリック</button>
}

// イベントオブジェクトを使う場合
function Button(): JSX.Element {
  const handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => {
    console.log('クリックされた位置:', event.clientX, event.clientY)
  }

  return <button onClick={handleClick}>クリック</button>
}

よく使うイベント

JSX
function EventExamples() {
  return (
    <div>
      {/* クリック */}
      <button onClick={() => console.log('クリック')}>
        ボタン
      </button>

      {/* ダブルクリック */}
      <button onDoubleClick={() => console.log('ダブルクリック')}>
        ダブルクリック
      </button>

      {/* マウスオーバー */}
      <div onMouseEnter={() => console.log('マウス入った')}>
        ホバーしてみて
      </div>

      {/* 入力変更 */}
      <input onChange={(e) => console.log(e.target.value)} />

      {/* フォーカス */}
      <input onFocus={() => console.log('フォーカス')} />

      {/* キー押下 */}
      <input onKeyDown={(e) => console.log(e.key)} />

      {/* フォーム送信 */}
      <form onSubmit={(e) => {
        e.preventDefault()
        console.log('送信')
      }}>
        <button type="submit">送信</button>
      </form>
    </div>
  )
}

イベントの注意点

❌ 関数を実行してしまう:

JSX
<button onClick={handleClick()}>クリック</button>
// handleClick()が即座に実行される

✅ 関数を渡す:

JSX
<button onClick={handleClick}>クリック</button>
// クリック時にhandleClickが実行される

引数を渡したい場合:

JSX
function App() {
  const handleClick = (message) => {
    alert(message)
  }

  return (
    <>
      {/* アロー関数で囲む */}
      <button onClick={() => handleClick('こんにちは')}>
        挨拶
      </button>
      
      {/* bind を使う */}
      <button onClick={handleClick.bind(null, 'さようなら')}>
        別れ
      </button>
    </>
  )
}

7. 実践例:インタラクティブなカード

ここまで学んだJSXの知識を組み合わせて、実際に動くインタラクティブなコンポーネントを作成してみましょう。

このカードコンポーネントでは、状態管理(useState)、条件付きレンダリング、イベント処理を組み合わせて、ユーザーの操作に応じて表示が変化するUIを実装します。

JavaScript版:

JSX
import { useState } from 'react'

function InteractiveCard() {
  const [isExpanded, setIsExpanded] = useState(false)
  const [likes, setLikes] = useState(0)

  return (
    <div className="card">
      <h2>React入門</h2>
      <p>
        Reactは、ユーザーインターフェースを構築するためのJavaScriptライブラリです。
        {isExpanded && (
          <span>
            コンポーネントベースで、再利用可能なUIを簡単に作成できます。
            Facebookによって開発され、多くの企業で採用されています。
          </span>
        )}
      </p>
      
      <div className="actions">
        <button onClick={() => setIsExpanded(!isExpanded)}>
          {isExpanded ? '閉じる' : 'もっと読む'}
        </button>
        
        <button onClick={() => setLikes(likes + 1)}>
          ❤️ {likes}
        </button>
      </div>
    </div>
  )
}

TypeScript版:

TSX
import { useState } from 'react'

function InteractiveCard(): JSX.Element {
  const [isExpanded, setIsExpanded] = useState<boolean>(false)
  const [likes, setLikes] = useState<number>(0)

  return (
    <div className="card">
      <h2>React入門</h2>
      <p>
        Reactは、ユーザーインターフェースを構築するためのJavaScriptライブラリです。
        {isExpanded && (
          <span>
            コンポーネントベースで、再利用可能なUIを簡単に作成できます。
            Facebookによって開発され、多くの企業で採用されています。
          </span>
        )}
      </p>
      
      <div className="actions">
        <button onClick={() => setIsExpanded(!isExpanded)}>
          {isExpanded ? '閉じる' : 'もっと読む'}
        </button>
        
        <button onClick={() => setLikes(likes + 1)}>
          ❤️ {likes}
        </button>
      </div>
    </div>
  )
}

まとめ

この記事では、JSXの詳細な書き方とルールを学びました。

重要なポイント:

  • 一つの親要素で囲む(Fragmentの活用)
  • すべてのタグを閉じる
  • className、キャメルケースを使う
  • 波括弧{}でJavaScriptを埋め込む
  • 条件付きレンダリング(三項演算子、&&)
  • リスト表示には一意のkeyが必要
  • イベントハンドラは関数を渡す

JSXのベストプラクティス:

  • Fragmentで不要なDOM要素を避ける
  • 複雑な条件分岐は関数に切り出す
  • indexをkeyにしない
  • イベントハンドラには意味のある名前をつける

次のステップ: 次回は、コンポーネントの詳細と、propsを使ったデータの受け渡しについて学びます。JSXの知識を活かして、再利用可能なコンポーネントを作っていきましょう!

JSXは最初は戸惑うかもしれませんが、慣れると非常に直感的で便利です。実際にコードを書いて、たくさん練習してみてください!