目錄
- 什麼是 React?
- 核心概念
- 快速開始
- JSX 語法
- 元件(Components)
- Props 屬性
- State 狀態
- Hooks 鉤子
- 事件處理
- 條件渲染
- 列表渲染
- 表單處理
- 生命週期
- Context API
- 效能優化
- 常用第三方庫
- 最佳實踐
什麼是 React?
React 是用於建構使用者介面的 JavaScript 函式庫,由 Facebook 於 2013 年開源。
核心特點
- 🎯 宣告式:描述 UI 應該是什麼樣子,React 負責更新
- 🧩 元件化:將 UI 拆分成獨立、可重用的元件
- 📚 一次學習,到處使用:React Native、React VR 等
- ⚡ Virtual DOM:高效的 DOM 更新機制
為什麼選擇 React?
傳統 jQuery 方式:
// 命令式:告訴瀏覽器「怎麼做」
$('#button').click(function() {
$('#counter').text(parseInt($('#counter').text()) + 1)
})
React 方式:
// 宣告式:描述「是什麼」
function Counter() {
const [count, setCount] = useState(0)
return <div>{count}</div> // UI 自動更新
}
核心概念
1. Virtual DOM
使用者操作
↓
更新 State
↓
React 建立新的 Virtual DOM
↓
與舊 Virtual DOM 比較(Diffing)
↓
只更新變化的部分到真實 DOM
2. 單向資料流
Props(由上往下傳遞)
↓
Parent Component
↓
Child Component
↓
Event(由下往上通知)
3. 元件思維
<App>
<Header />
<Main>
<Sidebar />
<Content>
<Article />
<Comments />
</Content>
</Main>
<Footer />
</App>
快速開始
建立 React 專案
# 使用 Create React App
npx create-react-app my-app
cd my-app
npm start
# 使用 Vite(推薦,更快)
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev
第一個 React 應用
// src/App.jsx
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
return (
<div>
<h1>Hello React!</h1>
<p>計數:{count}</p>
<button onClick={() => setCount(count + 1)}>
點擊 +1
</button>
</div>
)
}
export default App
JSX 語法
什麼是 JSX?
JSX = JavaScript + XML,是 JavaScript 的語法擴展。
// JSX
const element = <h1>Hello, World!</h1>
// 編譯後的 JavaScript
const element = React.createElement('h1', null, 'Hello, World!')
JSX 基本規則
1. 必須有一個根元素
// ❌ 錯誤:多個根元素
return (
<h1>標題</h1>
<p>內容</p>
)
// ✅ 正確:使用 Fragment
return (
<>
<h1>標題</h1>
<p>內容</p>
</>
)
// ✅ 正確:使用 div 包裹
return (
<div>
<h1>標題</h1>
<p>內容</p>
</div>
)
2. 使用 {} 嵌入 JavaScript 表達式
const name = 'Alice'
const age = 25
return (
<div>
<p>姓名:{name}</p>
<p>年齡:{age}</p>
<p>明年:{age + 1} 歲</p>
<p>{name.toUpperCase()}</p>
</div>
)
3. className 而非 class
// ❌ 錯誤
<div class="container"></div>
// ✅ 正確
<div className="container"></div>
4. 駝峰式命名
// HTML 屬性用駝峰式
<div
className="box"
onClick={handleClick}
onMouseEnter={handleHover}
tabIndex={0}
/>
// CSS 屬性也用駝峰式
<div style={{
backgroundColor: 'blue',
fontSize: '16px'
}} />
5. 自閉合標籤必須加斜線
// ❌ 錯誤
<img src="logo.png">
<input type="text">
// ✅ 正確
<img src="logo.png" />
<input type="text" />
元件(Components)
函式元件(推薦)
// 基本函式元件
function Welcome() {
return <h1>Hello!</h1>
}
// 箭頭函式元件
const Welcome = () => {
return <h1>Hello!</h1>
}
// 簡寫(單行)
const Welcome = () => <h1>Hello!</h1>
類別元件(較舊的方式)
import React, { Component } from 'react'
class Welcome extends Component {
render() {
return <h1>Hello!</h1>
}
}
元件命名規則
// ✅ 正確:大寫開頭
function UserProfile() { }
const ProductCard = () => { }
// ❌ 錯誤:小寫開頭(會被當作 HTML 標籤)
function userProfile() { }
Props 屬性
Props(Properties)是什麼?
- Props 是父元件傳遞給子元件的資料
- 類似函式的參數:父元件呼叫子元件時「傳入」的值
- 特性:只能讀取,不能修改(唯讀)
父元件 → [傳遞 Props] → 子元件
↓
子元件使用 Props 顯示內容
傳遞 Props 的基本用法
// 父元件:傳遞資料給子元件
function App() {
return (
<UserCard
name="Alice" // 傳遞字串
age={25} // 傳遞數字(用 {} 包起來)
isAdmin={true} // 傳遞布林值
hobbies={['coding', 'reading']} // 傳遞陣列
/>
)
}
// 子元件:接收 props 參數
function UserCard(props) {
// props 是一個物件,包含所有傳入的屬性
// props = {
// name: "Alice",
// age: 25,
// isAdmin: true,
// hobbies: ['coding', 'reading']
// }
return (
<div>
<h2>{props.name}</h2>
<p>年齡:{props.age}</p>
<p>管理員:{props.isAdmin ? '是' : '否'}</p>
<ul>
{props.hobbies.map((hobby, index) => (
<li key={index}>{hobby}</li>
))}
</ul>
</div>
)
}
傳遞不同類型的資料:
<Component
text="Hello" // 字串:可以不用 {}
number={42} // 數字:必須用 {}
boolean={true} // 布林:必須用 {}
array={[1, 2, 3]} // 陣列:必須用 {}
object={{ key: 'value' }} // 物件:必須用 {}
function={handleClick} // 函式:必須用 {}
/>
解構 Props(推薦寫法)
為什麼要解構?
- 避免重複寫
props. - 程式碼更簡潔易讀
- 一眼看出元件需要哪些資料
// ❌ 不解構:需要一直寫 props.xxx
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>年齡:{props.age}</p>
<p>管理員:{props.isAdmin ? '是' : '否'}</p>
</div>
)
}
// ✅ 解構:直接使用變數名稱
function UserCard({ name, age, isAdmin }) {
// 直接從 props 物件中取出需要的屬性
return (
<div>
<h2>{name}</h2> {/* 不用寫 props.name */}
<p>年齡:{age}</p> {/* 不用寫 props.age */}
<p>管理員:{isAdmin ? '是' : '否'}</p>
</div>
)
}
解構的原理:
// 這兩種寫法是等價的
// 方式 1:接收 props 物件
function UserCard(props) {
const name = props.name
const age = props.age
// ...使用 name, age
}
// 方式 2:直接在參數中解構(推薦)
function UserCard({ name, age }) {
// 直接使用 name, age
}
預設值(Default Props)
為什麼需要預設值?
- 當父元件沒有傳遞某些 Props 時,使用預設值
- 避免出現
undefined錯誤 - 讓元件更容易使用
// 使用 = 設定預設值
function Button({ text = '按鈕', color = 'blue', size = 'medium' }) {
// ^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
// 如果沒傳 text 如果沒傳 color 如果沒傳 size
// 就用這個預設值 就用這個預設值 就用這個預設值
return (
<button style={{ color, fontSize: size === 'large' ? '20px' : '16px' }}>
{text}
</button>
)
}
// 使用範例
<Button />
// 結果:顯示「按鈕」,藍色,中等大小
// 因為所有屬性都使用預設值
<Button text="送出" />
// 結果:顯示「送出」,藍色(使用預設值),中等大小(使用預設值)
<Button text="刪除" color="red" />
// 結果:顯示「刪除」,紅色,中等大小(使用預設值)
<Button text="確認" color="green" size="large" />
// 結果:顯示「確認」,綠色,大字體
// 所有屬性都有傳入,不使用預設值
預設值的運作方式:
// 當你這樣呼叫
<Button color="red" />
// 元件接收到的實際上是
function Button({ text = '按鈕', color = 'red', size = 'medium' }) {
// text 沒傳 → 使用預設值 '按鈕'
// color 有傳 'red' → 使用傳入的值 'red'
// size 沒傳 → 使用預設值 'medium'
}
完整範例:
// 定義元件
function Greeting({ name = '訪客', timeOfDay = '早安' }) {
return <h1>{timeOfDay},{name}!</h1>
}
// 使用方式和結果
<Greeting />
// 顯示:早安,訪客!
<Greeting name="Alice" />
// 顯示:早安,Alice!
<Greeting name="Bob" timeOfDay="晚安" />
// 顯示:晚安,Bob!
Props.children
// 父元件
function Card({ children }) {
return (
<div className="card">
{children}
</div>
)
}
// 使用
<Card>
<h1>標題</h1>
<p>內容</p>
</Card>
Props 注意事項
// ✅ Props 是唯讀的
function Welcome(props) {
// ❌ 不能修改 props
props.name = 'New Name' // 錯誤!
// ✅ 可以基於 props 計算新值
const greeting = `Hello, ${props.name}`
return <h1>{greeting}</h1>
}
State 狀態
State 是什麼?
- State = 元件的「記憶」或「內部狀態」
- 是元件自己管理的可變資料
- 當 State 改變時,React 會自動重新渲染元件,更新畫面
Props vs State 的差異:
Props(屬性) State(狀態)
------------------- -------------------
從父元件傳入 元件自己管理
唯讀,不可修改 可以修改
類似函式的「參數」 類似變數的「值」
父元件控制 元件自己控制
範例:
Props: 使用者名稱 State: 計數器數值
Props: 按鈕文字 State: 表單輸入內容
Props: 圖片網址 State: 是否顯示下拉選單
useState Hook 基本用法
什麼是 Hook?
- Hook 是 React 16.8 新增的特性
- 讓函式元件也能使用 State 和其他 React 功能
useState是最基本的 Hook,用來管理狀態
import { useState } from 'react'
function Counter() {
// 宣告一個 state 變數
const [count, setCount] = useState(0)
// ^^^^^ ^^^^^^^^ ^
// | | 初始值(第一次渲染時的值)
// | 更新函式(用來修改 count 的函式)
// 當前狀態值(目前的數字)
return (
<div>
<p>計數:{count}</p>
<button onClick={() => setCount(count + 1)}>
+1
</button>
</div>
)
}
運作流程:
1. 初始化:count = 0
2. 使用者點擊按鈕
3. 呼叫 setCount(count + 1) → count 變成 1
4. React 偵測到 state 改變
5. React 重新渲染元件
6. 畫面更新,顯示新的 count 值
useState 的完整解釋:
const [count, setCount] = useState(0)
// ^^^^^ ^^^^^^^^ ^^^
// │ │ └─ 初始值
// │ └─ 更新函式(慣例命名:set + 狀態名稱)
// └─ 狀態變數(可以取任何名字)
// 這是 JavaScript 的「陣列解構」語法
// 等同於:
const stateArray = useState(0)
const count = stateArray[0] // 狀態值
const setCount = stateArray[1] // 更新函式
實際範例:
function LikeButton() {
// 初始狀態:未按讚(false)
const [liked, setLiked] = useState(false)
return (
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️ 已按讚' : '🤍 按讚'}
</button>
)
}
// 點擊流程:
// 1. liked = false → 顯示「🤍 按讚」
// 2. 點擊按鈕 → setLiked(!liked) → liked = true
// 3. React 重新渲染 → 顯示「❤️ 已按讚」
// 4. 再次點擊 → setLiked(!liked) → liked = false
// 5. 又顯示「🤍 按讚」
多個 State
function UserForm() {
const [name, setName] = useState('')
const [age, setAge] = useState(0)
const [email, setEmail] = useState('')
return (
<form>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
value={age}
onChange={(e) => setAge(e.target.value)}
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</form>
)
}
物件 State
function UserForm() {
const [user, setUser] = useState({
name: '',
age: 0,
email: ''
})
// 更新物件 state(必須使用展開運算子)
const handleNameChange = (e) => {
setUser({
...user, // 保留其他屬性
name: e.target.value // 只更新 name
})
}
return (
<input
value={user.name}
onChange={handleNameChange}
/>
)
}
陣列 State
function TodoList() {
const [todos, setTodos] = useState([])
// 新增項目
const addTodo = (text) => {
setTodos([...todos, { id: Date.now(), text }])
}
// 刪除項目
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id))
}
// 更新項目
const updateTodo = (id, newText) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, text: newText } : todo
))
}
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
)
}
State 更新是非同步的
function Counter() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
console.log(count) // ⚠️ 仍是舊值!
}
// ✅ 使用 useEffect 監聽變化
useEffect(() => {
console.log('count 已更新:', count)
}, [count])
}
函式式更新
function Counter() {
const [count, setCount] = useState(0)
const handleClick = () => {
// ❌ 可能有問題(基於舊值)
setCount(count + 1)
setCount(count + 1) // 不會 +2
// ✅ 正確方式(函式式更新)
setCount(prevCount => prevCount + 1)
setCount(prevCount => prevCount + 1) // 會 +2
}
}
Hooks 鉤子
Hooks 是什麼?
- Hooks(鉤子) 是 React 16.8 引入的新功能
- 讓函式元件也能使用 state 和其他 React 特性
- 名稱都以
use開頭(如useState,useEffect) - 必須在元件的最上層呼叫,不能在條件式或迴圈中使用
為什麼叫 "Hook"(鉤子)?
Hook = 讓你「鉤入」React 功能的函式
useState → 鉤入「狀態管理」功能
useEffect → 鉤入「副作用」功能
useContext → 鉤入「Context」功能
Hooks 使用規則:
// ✅ 正確:在元件最上層使用
function MyComponent() {
const [count, setCount] = useState(0)
useEffect(() => { /* ... */ })
return <div>{count}</div>
}
// ❌ 錯誤:在條件式中使用
function MyComponent() {
if (condition) {
const [count, setCount] = useState(0) // 錯誤!
}
}
// ❌ 錯誤:在迴圈中使用
function MyComponent() {
for (let i = 0; i < 5; i++) {
useEffect(() => { /* ... */ }) // 錯誤!
}
}
useState - 狀態管理
用途:讓元件「記住」資料
語法:
const [state, setState] = useState(initialValue)
// ^^^^^ ^^^^^^^^ ^^^^^^^^^^^^
// 狀態值 更新函式 初始值
常見使用場景:
// 1. 計數器
const [count, setCount] = useState(0)
// 2. 開關狀態
const [isOpen, setIsOpen] = useState(false)
// 3. 輸入框內容
const [text, setText] = useState('')
// 4. 選中的項目
const [selected, setSelected] = useState(null)
// 5. 列表資料
const [items, setItems] = useState([])
詳細說明:參考前面的 State 狀態 章節
useEffect - 副作用處理
副作用(Side Effect)是什麼?
- 在 React 中,「副作用」指的是與渲染畫面無關的操作
- 例如:資料獲取、訂閱、手動修改 DOM、計時器等
常見副作用:
✓ 從 API 獲取資料
✓ 訂閱 WebSocket
✓ 設定計時器(setTimeout, setInterval)
✓ 手動修改 DOM(focus、scroll)
✓ 記錄日誌(console.log)
✓ 儲存到 localStorage
語法:
useEffect(() => {
// 副作用程式碼
return () => {
// 清理函式(cleanup)
}
}, [dependencies])
// ^^^^^^^^^^^^^^
// 依賴陣列:決定何時執行
useEffect 的三種模式:
import { useEffect, useState } from 'react'
function Example() {
const [count, setCount] = useState(0)
// 模式 1:每次渲染後都執行
useEffect(() => {
console.log('元件已渲染')
})
// 沒有依賴陣列 → 每次渲染都執行
// 模式 2:只在首次渲染執行
useEffect(() => {
console.log('元件已掛載(第一次渲染)')
}, [])
// ^^ 空陣列 → 只執行一次
// 模式 3:當特定值變化時執行
useEffect(() => {
console.log('count 已變更:', count)
}, [count])
// ^^^^^^^ 依賴 count → count 變化時執行
return <div>{count}</div>
}
依賴陣列的作用:
沒有陣列 → 每次渲染都執行
useEffect(() => { ... })
空陣列 [] → 只在首次渲染執行(類似 componentDidMount)
useEffect(() => { ... }, [])
有值 [count] → 當 count 變化時執行
useEffect(() => { ... }, [count])
多個值 → 當任何一個值變化時執行
useEffect(() => { ... }, [count, name, isOpen])
實際範例:
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
// 當 userId 改變時,重新獲取使用者資料
useEffect(() => {
console.log('獲取使用者資料:', userId)
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data))
}, [userId])
// ^^^^^^^^ 依賴 userId
// 執行時機:
// 1. 第一次渲染:userId = 1 → 獲取 user 1 的資料
// 2. userId 變成 2 → 再次執行,獲取 user 2 的資料
// 3. userId 沒變 → 不執行
// 4. userId 變成 3 → 再次執行,獲取 user 3 的資料
return <div>{user?.name}</div>
}
清理函式(Cleanup Function)
為什麼需要清理?
- 避免記憶體洩漏
- 取消未完成的請求
- 清除計時器
- 取消訂閱
function Timer() {
const [seconds, setSeconds] = useState(0)
useEffect(() => {
// 設定計時器
const timer = setInterval(() => {
setSeconds(s => s + 1)
}, 1000)
// 清理函式:元件卸載時執行
return () => {
clearInterval(timer) // 清除計時器
console.log('計時器已清除')
}
}, [])
return <div>{seconds} 秒</div>
}
// 執行順序:
// 1. 元件掛載 → 建立計時器
// 2. 元件卸載 → 執行清理函式 → 清除計時器
清理函式的執行時機:
1. 元件卸載時(元件從畫面上移除)
2. 下次 effect 執行前(依賴改變時)
3. 確保舊的副作用被清理
範例:
useEffect(() => {
console.log('訂閱', props.userId)
return () => console.log('取消訂閱', props.userId)
}, [props.userId])
// props.userId = 1 → 訂閱 1
// props.userId = 2 → 取消訂閱 1 → 訂閱 2
// props.userId = 3 → 取消訂閱 2 → 訂閱 3
// 元件卸載 → 取消訂閱 3
useEffect 常見用途
// 1. 資料獲取
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data))
}, [])
// 2. 訂閱
useEffect(() => {
const subscription = props.source.subscribe()
return () => subscription.unsubscribe()
}, [props.source])
// 3. 手動操作 DOM
useEffect(() => {
document.title = `計數:${count}`
}, [count])
// 4. 監聽事件
useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth)
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
useContext - 跨元件傳遞資料
import { createContext, useContext } from 'react'
// 建立 Context
const UserContext = createContext()
// 父元件提供資料
function App() {
const user = { name: 'Alice', age: 25 }
return (
<UserContext.Provider value={user}>
<ChildComponent />
</UserContext.Provider>
)
}
// 子元件使用資料
function ChildComponent() {
const user = useContext(UserContext)
return <div>{user.name}</div>
}
useRef - 引用 DOM 元素
import { useRef } from 'react'
function TextInput() {
const inputRef = useRef(null)
const handleFocus = () => {
inputRef.current.focus()
}
return (
<>
<input ref={inputRef} />
<button onClick={handleFocus}>聚焦輸入框</button>
</>
)
}
useReducer - 複雜狀態管理
import { useReducer } from 'react'
// Reducer 函式
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
case 'reset':
return { count: 0 }
default:
return state
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div>
<p>計數:{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</div>
)
}
useMemo - 效能優化(記憶計算結果)
import { useMemo } from 'react'
function ExpensiveComponent({ data }) {
// 只在 data 變化時重新計算
const processedData = useMemo(() => {
console.log('處理資料中...')
return data.map(item => item * 2)
}, [data])
return <div>{processedData}</div>
}
useCallback - 效能優化(記憶函式)
import { useCallback } from 'react'
function ParentComponent() {
const [count, setCount] = useState(0)
// 只在 count 變化時建立新函式
const handleClick = useCallback(() => {
console.log('clicked', count)
}, [count])
return <ChildComponent onClick={handleClick} />
}
自訂 Hook
// useLocalStorage.js
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const saved = localStorage.getItem(key)
return saved ? JSON.parse(saved) : initialValue
})
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value))
}, [key, value])
return [value, setValue]
}
// 使用
function App() {
const [name, setName] = useLocalStorage('name', '')
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
)
}
事件處理
基本事件處理
function Button() {
// 事件處理函式
const handleClick = (e) => {
e.preventDefault() // 阻止預設行為
console.log('按鈕被點擊')
}
return <button onClick={handleClick}>點我</button>
}
傳遞參數
function ItemList() {
const handleDelete = (id) => {
console.log('刪除項目:', id)
}
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
{/* 方法一:箭頭函式 */}
<button onClick={() => handleDelete(item.id)}>
刪除
</button>
{/* 方法二:bind */}
<button onClick={handleDelete.bind(null, item.id)}>
刪除
</button>
</li>
))}
</ul>
)
}
常見事件
function EventExamples() {
return (
<div>
{/* 滑鼠事件 */}
<button onClick={handleClick}>點擊</button>
<div onMouseEnter={handleHover}>懸停</div>
<div onMouseLeave={handleLeave}>離開</div>
{/* 鍵盤事件 */}
<input onKeyDown={handleKeyDown} />
<input onKeyUp={handleKeyUp} />
<input onKeyPress={handleKeyPress} />
{/* 表單事件 */}
<input onChange={handleChange} />
<form onSubmit={handleSubmit} />
<input onFocus={handleFocus} />
<input onBlur={handleBlur} />
{/* 其他事件 */}
<div onScroll={handleScroll} />
<img onLoad={handleLoad} />
</div>
)
}
條件渲染
if/else 語句
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>歡迎回來!</h1>
} else {
return <h1>請登入</h1>
}
}
三元運算子
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<h1>歡迎回來!</h1>
) : (
<h1>請登入</h1>
)}
</div>
)
}
&& 邏輯運算子
function MessageBox({ hasMessage, message }) {
return (
<div>
{hasMessage && <p>{message}</p>}
</div>
)
}
多條件渲染
function StatusBadge({ status }) {
return (
<div>
{status === 'success' && <span className="badge-success">成功</span>}
{status === 'error' && <span className="badge-error">錯誤</span>}
{status === 'pending' && <span className="badge-pending">處理中</span>}
</div>
)
}
列表渲染
基本列表
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
Key 的重要性
// ❌ 錯誤:使用索引作為 key(可能導致問題)
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
// ✅ 正確:使用唯一 ID
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
複雜列表
function ProductList({ products }) {
return (
<div className="products">
{products.map(product => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.price} 元</p>
<button>加入購物車</button>
</div>
))}
</div>
)
}
表單處理
受控元件
function LoginForm() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
console.log('登入:', username, password)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="使用者名稱"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="密碼"
/>
<button type="submit">登入</button>
</form>
)
}
多個輸入欄位
function UserForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
age: ''
})
const handleChange = (e) => {
const { name, value } = e.target
setFormData({
...formData,
[name]: value
})
}
return (
<form>
<input
name="name"
value={formData.name}
onChange={handleChange}
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
/>
<input
name="age"
value={formData.age}
onChange={handleChange}
/>
</form>
)
}
Checkbox 和 Radio
function PreferencesForm() {
const [newsletter, setNewsletter] = useState(false)
const [theme, setTheme] = useState('light')
return (
<form>
{/* Checkbox */}
<label>
<input
type="checkbox"
checked={newsletter}
onChange={(e) => setNewsletter(e.target.checked)}
/>
訂閱電子報
</label>
{/* Radio */}
<div>
<label>
<input
type="radio"
value="light"
checked={theme === 'light'}
onChange={(e) => setTheme(e.target.value)}
/>
淺色主題
</label>
<label>
<input
type="radio"
value="dark"
checked={theme === 'dark'}
onChange={(e) => setTheme(e.target.value)}
/>
深色主題
</label>
</div>
</form>
)
}
生命週期
函式元件生命週期(使用 useEffect)
function LifecycleDemo() {
const [count, setCount] = useState(0)
// 1. 元件掛載(componentDidMount)
useEffect(() => {
console.log('元件已掛載')
}, [])
// 2. 元件更新(componentDidUpdate)
useEffect(() => {
console.log('count 已更新:', count)
}, [count])
// 3. 元件卸載(componentWillUnmount)
useEffect(() => {
return () => {
console.log('元件即將卸載')
}
}, [])
// 4. 每次渲染後
useEffect(() => {
console.log('元件已渲染')
})
}
完整生命週期範例
function DataFetcher({ userId }) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// 掛載或 userId 變化時執行
setLoading(true)
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setData(data)
setLoading(false)
})
// 清理函式(卸載或下次執行前)
return () => {
console.log('清理舊的請求')
}
}, [userId])
if (loading) return <div>載入中...</div>
return <div>{data.name}</div>
}
Context API
建立和使用 Context
import { createContext, useContext, useState } from 'react'
// 1. 建立 Context
const ThemeContext = createContext()
// 2. 建立 Provider 元件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light')
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
// 3. 建立自訂 Hook
function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme 必須在 ThemeProvider 內使用')
}
return context
}
// 4. 在應用中使用
function App() {
return (
<ThemeProvider>
<Header />
<Main />
</ThemeProvider>
)
}
function Header() {
const { theme, toggleTheme } = useTheme()
return (
<header className={theme}>
<button onClick={toggleTheme}>切換主題</button>
</header>
)
}
效能優化
React.memo - 防止不必要的重渲染
import { memo } from 'react'
// 只在 props 變化時重新渲染
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
console.log('重新渲染')
return <div>{data}</div>
})
// 自訂比較函式
const ExpensiveComponent = memo(
function ExpensiveComponent({ data }) {
return <div>{data}</div>
},
(prevProps, nextProps) => {
// 回傳 true 表示不重渲染
return prevProps.data === nextProps.data
}
)
useMemo - 記憶計算結果
function DataProcessor({ data }) {
// 只在 data 變化時重新計算
const processedData = useMemo(() => {
console.log('處理資料...')
return data.map(item => item * 2)
}, [data])
return <div>{processedData}</div>
}
useCallback - 記憶函式
function ParentComponent() {
const [count, setCount] = useState(0)
// 避免每次渲染都建立新函式
const handleClick = useCallback(() => {
console.log('clicked')
}, [])
return <ChildComponent onClick={handleClick} />
}
const ChildComponent = memo(({ onClick }) => {
console.log('子元件渲染')
return <button onClick={onClick}>點我</button>
})
程式碼分割(Code Splitting)
import { lazy, Suspense } from 'react'
// 動態引入元件
const HeavyComponent = lazy(() => import('./HeavyComponent'))
function App() {
return (
<Suspense fallback={<div>載入中...</div>}>
<HeavyComponent />
</Suspense>
)
}
常用第三方庫
React Router - 路由管理
npm install react-router-dom
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">首頁</Link>
<Link to="/about">關於</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
</BrowserRouter>
)
}
Axios - HTTP 請求
npm install axios
import axios from 'axios'
import { useEffect, useState } from 'react'
function UserList() {
const [users, setUsers] = useState([])
useEffect(() => {
axios.get('/api/users')
.then(response => setUsers(response.data))
.catch(error => console.error(error))
}, [])
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
React Query - 資料獲取
npm install @tanstack/react-query
import { useQuery } from '@tanstack/react-query'
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(res => res.json())
})
if (isLoading) return <div>載入中...</div>
if (error) return <div>錯誤:{error.message}</div>
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
Zustand - 狀態管理
npm install zustand
import create from 'zustand'
// 建立 store
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 }))
}))
// 使用 store
function Counter() {
const { count, increment, decrement } = useStore()
return (
<div>
<p>{count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
最佳實踐
1. 元件設計原則
// ✅ 單一職責:每個元件只做一件事
function UserAvatar({ url, size }) { }
function UserName({ name }) { }
function UserCard({ user }) {
return (
<>
<UserAvatar url={user.avatar} size="large" />
<UserName name={user.name} />
</>
)
}
// ❌ 避免:一個元件做太多事
function UserEverything() {
// 處理使用者、訂單、評論...
}
2. Props 驗證(使用 TypeScript)
interface ButtonProps {
text: string
onClick: () => void
variant?: 'primary' | 'secondary'
}
function Button({ text, onClick, variant = 'primary' }: ButtonProps) {
return <button onClick={onClick}>{text}</button>
}
3. 避免在渲染中建立函式
// ❌ 每次渲染都建立新函式
function Parent() {
return <Child onClick={() => console.log('click')} />
}
// ✅ 使用 useCallback
function Parent() {
const handleClick = useCallback(() => {
console.log('click')
}, [])
return <Child onClick={handleClick} />
}
4. 合理拆分元件
// ✅ 好的拆分
function ProductCard({ product }) {
return (
<div>
<ProductImage image={product.image} />
<ProductInfo name={product.name} price={product.price} />
<ProductActions productId={product.id} />
</div>
)
}
// ❌ 過度拆分(每個 div 都是元件)
5. 使用解構和預設值
// ✅ 清晰易讀
function Button({
text,
onClick,
variant = 'primary',
disabled = false
}) {
return <button onClick={onClick}>{text}</button>
}
// ❌ 難以維護
function Button(props) {
return <button onClick={props.onClick}>{props.text}</button>
}
6. 條件渲染技巧
// ✅ 早期返回
function UserProfile({ user }) {
if (!user) return <div>請登入</div>
if (user.banned) return <div>帳號已被封鎖</div>
return <div>{user.name}</div>
}
// ❌ 巢狀的條件
function UserProfile({ user }) {
return (
<div>
{user ? (
user.banned ? (
<div>帳號已被封鎖</div>
) : (
<div>{user.name}</div>
)
) : (
<div>請登入</div>
)}
</div>
)
}
7. 自訂 Hook 封裝邏輯
// ✅ 封裝可重用邏輯
function useFetch(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data)
setLoading(false)
})
}, [url])
return { data, loading }
}
// 使用
function Users() {
const { data, loading } = useFetch('/api/users')
if (loading) return <div>載入中...</div>
return <div>{data}</div>
}
總結
React 核心概念
React = 宣告式 + 元件化 + 單向資料流
元件:UI 的積木
Props:元件的輸入(唯讀)
State:元件的記憶(可變)
Hooks:函式元件的超能力
學習路徑
1. JSX 語法 ✓
2. 元件和 Props ✓
3. State 和事件處理 ✓
4. 列表和條件渲染 ✓
5. Hooks(useState, useEffect)✓
6. 表單處理 ✓
7. 效能優化 ✓
8. Router 和狀態管理
9. TypeScript
10. 測試
快速參考
// 元件
function Component() { return <div>Hello</div> }
// State
const [value, setValue] = useState(0)
// Effect
useEffect(() => { /* 副作用 */ }, [依賴])
// 事件
<button onClick={handleClick}>
// 列表
{items.map(item => <li key={item.id}>{item}</li>)}
// 條件
{condition && <div>顯示</div>}
{condition ? <A /> : <B />}
參考資源
- 官方網站:https://react.dev/
- 舊版文件:https://legacy.reactjs.org/
- React Router:https://reactrouter.com/
- React Query:https://tanstack.com/query
- 中文教學:https://zh-hans.react.dev/
建立日期:2024-11-14 最後更新:2025-11-19(改善 Props、State、Hooks 說明) 適用版本:React 18.x