2.1 什麼是 JSX?

JSX(JavaScript XML)是 React 使用的語法擴展,讓你可以在 JavaScript 中撰寫類似 HTML 的程式碼。

// 這就是 JSX
const element = <h1>Hello, World!</h1>;

JSX 的本質 — 語法糖

JSX 會被編譯器(Babel / SWC)轉換為 React.createElement() 呼叫:

// 你寫的 JSX
const element = <h1 className="title">Hello!</h1>;

// 編譯後的 JavaScript
const element = React.createElement(
  'h1',                      // 標籤類型
  { className: 'title' },   // 屬性(props)
  'Hello!'                   // 子元素
);

// createElement 回傳的物件(React Element)
const element = {
  type: 'h1',
  props: {
    className: 'title',
    children: 'Hello!'
  }
};

你不需要手動呼叫 createElement,JSX 會幫你做這件事。


2.2 JSX 基本規則

規則 1:必須有一個根元素

// ❌ 錯誤:多個根元素
function App() {
  return (
    <h1>標題</h1>
    <p>段落</p>
  );
}

// ✅ 正確:用 <div> 包裹
function App() {
  return (
    <div>
      <h1>標題</h1>
      <p>段落</p>
    </div>
  );
}

// ✅ 更好:用 Fragment 避免多餘的 DOM 節點
function App() {
  return (
    <>
      <h1>標題</h1>
      <p>段落</p>
    </>
  );
}

// ✅ 等同於上面的寫法
import { Fragment } from 'react';

function App() {
  return (
    <Fragment>
      <h1>標題</h1>
      <p>段落</p>
    </Fragment>
  );
}

規則 2:所有標籤都必須關閉

// ❌ 錯誤:未關閉的標籤
<img src="photo.jpg">
<br>
<input type="text">

// ✅ 正確:自我關閉標籤
<img src="photo.jpg" />
<br />
<input type="text" />

規則 3:使用 camelCase 命名屬性

// HTML 屬性 → JSX 屬性
// class     → className
// for       → htmlFor
// onclick   → onClick
// tabindex  → tabIndex
// readonly  → readOnly

// 範例
function Form() {
  return (
    <form>
      <label htmlFor="email">Email:</label>
      <input
        id="email"
        type="email"
        className="input-field"
        tabIndex={1}
        readOnly={false}
        autoComplete="email"
      />
    </form>
  );
}

2.3 在 JSX 中嵌入 JavaScript 表達式

使用 大括號 {} 在 JSX 中嵌入任何 JavaScript 表達式。

基本表達式

function Expressions() {
  const name = 'Alice';
  const age = 25;
  const hobbies = ['閱讀', '程式設計', '旅行'];

  return (
    <div>
      {/* 變數 */}
      <h1>姓名:{name}</h1>

      {/* 數學運算 */}
      <p>明年 {age + 1} 歲</p>

      {/* 函式呼叫 */}
      <p>名字大寫:{name.toUpperCase()}</p>

      {/* 字串模板 */}
      <p>{`${name} 今年 ${age} 歲`}</p>

      {/* 陣列長度 */}
      <p>有 {hobbies.length} 個興趣</p>

      {/* 三元運算子 */}
      <p>{age >= 18 ? '成年人' : '未成年'}</p>
    </div>
  );
}

注意:不能嵌入的東西

function InvalidExpressions() {
  return (
    <div>
      {/* ❌ 不能用 if/else 語句 */}
      {/* { if (true) { return 'yes' } } */}

      {/* ❌ 不能用 for 迴圈 */}
      {/* { for (let i = 0; i < 5; i++) {} } */}

      {/* ❌ 不能直接渲染物件 */}
      {/* { { name: 'Alice' } }  → 會報錯 */}

      {/* ✅ 物件要轉成字串才能渲染 */}
      <p>{JSON.stringify({ name: 'Alice' })}</p>

      {/* ✅ 布林值、null、undefined 不會被渲染 */}
      <p>{true}</p>    {/* 什麼都不顯示 */}
      <p>{false}</p>   {/* 什麼都不顯示 */}
      <p>{null}</p>    {/* 什麼都不顯示 */}
      <p>{undefined}</p> {/* 什麼都不顯示 */}
    </div>
  );
}

2.4 JSX 中的樣式

方法 1:className + CSS 檔案

/* App.css */
.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 16px;
  margin: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.card-title {
  font-size: 1.5rem;
  color: #333;
}

.highlight {
  background-color: #fff3cd;
}
import './App.css';

function Card() {
  const isHighlighted = true;

  return (
    <div className={`card ${isHighlighted ? 'highlight' : ''}`}>
      <h2 className="card-title">卡片標題</h2>
      <p>卡片內容</p>
    </div>
  );
}

方法 2:行內樣式(Inline Styles)

JSX 的行內樣式使用物件,屬性名使用 camelCase

function InlineStyleExample() {
  // 樣式物件
  const containerStyle = {
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    minHeight: '100vh',
    backgroundColor: '#f0f2f5',
  };

  const cardStyle = {
    padding: '24px',
    borderRadius: '12px',
    backgroundColor: 'white',
    boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
    maxWidth: '400px',
    width: '100%',
  };

  const titleStyle = {
    fontSize: '1.8rem',
    fontWeight: 'bold',
    color: '#1a1a2e',
    marginBottom: '12px',
  };

  return (
    <div style={containerStyle}>
      <div style={cardStyle}>
        <h1 style={titleStyle}>行內樣式範例</h1>
        <p style={{ color: '#666', lineHeight: 1.6 }}>
          JSX 的行內樣式使用 JavaScript 物件
        </p>
      </div>
    </div>
  );
}

方法 3:動態樣式

function DynamicStyle() {
  const score = 85;

  const getScoreColor = (score) => {
    if (score >= 90) return '#4caf50';  // 綠色
    if (score >= 70) return '#ff9800';  // 橘色
    return '#f44336';                    // 紅色
  };

  return (
    <div>
      <span
        style={{
          color: getScoreColor(score),
          fontSize: '2rem',
          fontWeight: 'bold',
        }}
      >
        {score} 分
      </span>
    </div>
  );
}

2.5 JSX 中的註解

function Comments() {
  return (
    <div>
      {/* 這是 JSX 中的註解 */}
      <h1>標題</h1>

      {/*
        多行註解
        也是用這種方式
      */}
      <p>內容</p>

      <input
        type="text"
        // 這種方式也可以(在屬性之間)
        placeholder="輸入文字"
      />
    </div>
  );
}

2.6 JSX 中的條件渲染(預覽)

function ConditionalRendering() {
  const isLoggedIn = true;
  const notifications = 5;
  const username = 'Alice';
  const role = 'admin';

  return (
    <div>
      {/* 三元運算子 */}
      <p>{isLoggedIn ? `歡迎回來,${username}!` : '請登入'}</p>

      {/* && 短路求值 */}
      {notifications > 0 && (
        <span>你有 {notifications} 則通知</span>
      )}

      {/* 多重條件 */}
      <p>
        權限:
        {role === 'admin' && '管理員'}
        {role === 'editor' && '編輯者'}
        {role === 'viewer' && '檢視者'}
      </p>
    </div>
  );
}

2.7 JSX 中的列表渲染(預覽)

function ListRendering() {
  const fruits = ['蘋果', '香蕉', '橘子', '葡萄', '西瓜'];

  const students = [
    { id: 1, name: 'Alice', grade: 95 },
    { id: 2, name: 'Bob', grade: 82 },
    { id: 3, name: 'Charlie', grade: 91 },
  ];

  return (
    <div>
      {/* 簡單列表 */}
      <h2>水果列表</h2>
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>

      {/* 物件列表 */}
      <h2>學生成績</h2>
      <table>
        <thead>
          <tr>
            <th>姓名</th>
            <th>成績</th>
          </tr>
        </thead>
        <tbody>
          {students.map((student) => (
            <tr key={student.id}>
              <td>{student.name}</td>
              <td>{student.grade}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

2.8 完整範例:個人名片

import './App.css';

function ProfileCard() {
  const profile = {
    name: 'Alice Chen',
    title: '前端工程師',
    company: 'Tech Corp',
    email: 'alice@techcorp.com',
    skills: ['React', 'TypeScript', 'Node.js', 'CSS'],
    experience: 3,
    isHiring: true,
  };

  const cardStyle = {
    maxWidth: '380px',
    margin: '40px auto',
    padding: '32px',
    borderRadius: '16px',
    backgroundColor: '#fff',
    boxShadow: '0 10px 30px rgba(0, 0, 0, 0.1)',
    fontFamily: "'Segoe UI', sans-serif",
  };

  const avatarStyle = {
    width: '80px',
    height: '80px',
    borderRadius: '50%',
    backgroundColor: '#667eea',
    color: 'white',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: '2rem',
    fontWeight: 'bold',
    margin: '0 auto 16px',
  };

  const skillTagStyle = {
    display: 'inline-block',
    padding: '4px 12px',
    margin: '4px',
    borderRadius: '20px',
    backgroundColor: '#e8eaf6',
    color: '#3f51b5',
    fontSize: '0.85rem',
  };

  return (
    <div style={cardStyle}>
      {/* 大頭貼 */}
      <div style={avatarStyle}>
        {profile.name.charAt(0)}
      </div>

      {/* 基本資訊 */}
      <h2 style={{ textAlign: 'center', marginBottom: '4px' }}>
        {profile.name}
      </h2>
      <p style={{ textAlign: 'center', color: '#666', marginTop: 0 }}>
        {profile.title} @ {profile.company}
      </p>

      {/* 經驗 */}
      <p style={{ textAlign: 'center', color: '#999' }}>
        {profile.experience} 年經驗
      </p>

      {/* 技能標籤 */}
      <div style={{ textAlign: 'center', marginTop: '16px' }}>
        {profile.skills.map((skill, index) => (
          <span key={index} style={skillTagStyle}>
            {skill}
          </span>
        ))}
      </div>

      {/* 聯絡資訊 */}
      <div style={{ marginTop: '20px', textAlign: 'center' }}>
        <a href={`mailto:${profile.email}`} style={{ color: '#667eea' }}>
          {profile.email}
        </a>
      </div>

      {/* 徵才狀態 */}
      {profile.isHiring && (
        <div style={{
          marginTop: '16px',
          padding: '8px',
          backgroundColor: '#e8f5e9',
          color: '#2e7d32',
          borderRadius: '8px',
          textAlign: 'center',
          fontSize: '0.9rem',
        }}>
          🟢 目前正在招募團隊成員
        </div>
      )}
    </div>
  );
}

export default ProfileCard;

2.9 練習題

練習 1:修正 JSX 錯誤

找出並修正以下程式碼的所有錯誤:

// 找出所有 JSX 語法錯誤
function BrokenComponent() {
  const message = "Hello";
  return (
    <h1 class="title">{message}</h1>
    <img src="photo.jpg">
    <label for="name">Name:</label>
    <input type="text" id="name" tabindex="1">
  )
}
參考答案
function FixedComponent() {
  const message = "Hello";
  return (
    <>
      <h1 className="title">{message}</h1>
      <img src="photo.jpg" />
      <label htmlFor="name">Name:</label>
      <input type="text" id="name" tabIndex={1} />
    </>
  );
}
修正項目: 1. 缺少根元素 → 加上 `<>...` 2. `class` → `className` 3. `` → ``(自我關閉) 4. `for` → `htmlFor` 5. `` → ``(自我關閉) 6. `tabindex` → `tabIndex`,`"1"` → `{1}`

練習 2:建立一個商品卡片

使用 JSX 建立一個商品卡片,包含:商品名稱、價格、描述、折扣計算、是否有庫存的條件顯示。


本課重點回顧

  1. JSX 是 React.createElement() 的語法糖
  2. 必須有一個根元素(可用 <> Fragment)
  3. 所有標籤必須關閉
  4. 屬性使用 camelCaseclassName, htmlFor, onClick
  5. 使用 {} 嵌入 JavaScript 表達式
  6. 行內樣式使用 JavaScript 物件