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. `練習 2:建立一個商品卡片
使用 JSX 建立一個商品卡片,包含:商品名稱、價格、描述、折扣計算、是否有庫存的條件顯示。
本課重點回顧
- JSX 是
React.createElement()的語法糖 - 必須有一個根元素(可用
<>Fragment) - 所有標籤必須關閉
- 屬性使用 camelCase(
className,htmlFor,onClick) - 使用
{}嵌入 JavaScript 表達式 - 行內樣式使用 JavaScript 物件