## 2025/9/05
### 📘 React 第 2 堂 — Hook 入門 & UI 進階
官方對應章節:《Describing the UI》(後半)+《Adding Interactivity》(開頭)
#### 1. Hook 基礎
**什麼是 Hook?**
- React 元件就是一個 function,每次畫面更新時會「重新執行」。
- 如果只用普通變數,function 每次重播都會「失憶」,之前的值會消失。
- **Hook(例如 useState)就是 React 提供的工具,讓元件能記住狀態。**
**Hook 的兩個限制**
- 只能在元件最外層呼叫
- React 是靠「呼叫順序」來記錄狀態。
- 如果把 Hook 放在 if / for 裡,順序會亂掉,React 就會搞混。
- 只能在 React 元件或其他 Hook 裡呼叫
- Hook 只能存在於 React 管理的 function 中,這樣 React 才能保存和更新狀態。
**範例**
```jsx
// ❌ 錯誤:放在 if 裡
function BadCounter({ show }) {
if (show) {
const [count, setCount] = useState(0);
}
return <p>這樣會壞掉</p>;
}
// ✅ 正確:放在最外層
function GoodCounter({ show }) {
const [count, setCount] = useState(0);
if (!show) return null;
return <button onClick={() => setCount(c => c + 1)}>+1 ({count})</button>;
}
```
**舉例說明**
`useState` 會幫你建立一個「狀態值」以及「更新這個狀態的方法」。
常見的寫法是:
```jsx
const [count, setCount] = useState(0);
```
1. `useState(0)`
- `useState` 是 React 的 **Hook**,用來在函式元件裡建立「狀態 (state)」。
- 這裡的 `0` 是「初始值」。
- 所以一開始 `sharedLikes = 0`。
2. 解構賦值 `[count, setCount]`
`useState` 會回傳一個 **陣列**,裡面有兩個東西:
1. **目前的狀態值** → 這裡叫 `count`
2. **更新狀態的函式** → 這裡叫 `setCount`
所以你用陣列解構,把它們取名出來。
等價於:
```jsx
const stateArray = useState(0);
const count = stateArray[0]; // 當前狀態值
const setCount = stateArray[1]; // 更新狀態的函式
```
3. 怎麼用?
- **讀取狀態** → 用 `Count`
- **更新狀態** → 呼叫 `setCount`
4. 和一般 js 的count 差別?
- **js** → 要手動更新 DOM `Count`才會更新
- **react**
- React 會更新 count 的值。
- React 會觸發「元件重新渲染」,畫面才會顯示最新的數字
---
#### 2. useState 進階:快照陷阱 & 函式型更新
**快照陷阱**
- React 的狀態更新不是立即的,而是「排隊等下次一併處理」。
- 多次 `setCount(count + 1)` 會用到「同一個舊值」,結果錯誤。
```jsx
// ❌ 快照陷阱:只會 +1
function CounterSnapshot() {
const [count, setCount] = useState(0);
const handleAddThree = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return <button onClick={handleAddThree}>+3(陷阱): {count}</button>;
}
```
**函式型更新(正確解法)**
- 使用 `setCount(c => c + 1)`,保證每次都用最新的值。
```jsx
// ✅ 修正版:函式型更新
function CounterFixed() {
const [count, setCount] = useState(0);
const handleAddThree = () => {
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
};
return <button onClick={handleAddThree}>+3(正確): {count}</button>;
}
```
**重點**
- React 更新是非同步批次處理。
- 需要依賴前一個值時,務必使用「函式型更新」。
---
#### 3. 狀態隔離 vs 狀態共享
**狀態隔離**
- 每個元件自己有自己的 state。
- 彼此之間互不干擾。
```jsx
function MyCounter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>+1 ({count})</button>;
}
function App() {
return (
<><MyCounter />
<MyCounter />
<MyCounter />
</>
);
}
```
**狀態共享**
- 把 state 提升到父元件集中管理。
- 子元件透過 props 使用相同的 state。
```jsx
function MyButton({ count, onClick }) {
return <button onClick={onClick}>+1 ({count})</button>;
}
function App() {
const [count, setCount] = useState(0);
return (
<><MyButton count={count} onClick={() => setCount(c => c + 1)} />
<MyButton count={count} onClick={() => setCount(c => c + 1)} />
</>
);
}
```
**重點**
- 狀態隔離:元件各自有獨立資料。
- 狀態共享:父元件集中管理,子元件同步更新。
:test_tube:
小測驗
- 兩種情況的重置如何設置?
---
#### 4. 條件式 Rendering
**概念**
- React 的判斷是「要不要產生元素」。
- 與傳統 `display:none` 不同,React 元素未被渲染就不會存在於 DOM。
**三種常用寫法**
```jsx
// if 語句
if (isLoggedIn) {
return <p>歡迎回來!</p>;
}
return <p>請先登入</p>;
// 三元運算子 ? :
<p>{isLoggedIn ? "歡迎回來!" : "請先登入"}</p>
// && 短路
<div>
<p>首頁</p>
{isLoading && <p>載入中...</p>}
</div>
```
**(1) if 語句**
語法:
```jsx
if (條件) {
return <元素A />;
} else {
return <元素B />;
}
```
- `if` 是 **語句 (statement)**,不能直接寫在 JSX 中。
- 通常放在 `return` 之前,決定整個 Component 要渲染什麼。
---
**(2) 三元運算子 ? :**
語法:
```jsx
{條件 ? <元素A /> : <元素B />}
```
- 有三個部分:**條件 ? true時的值 : false時的值**。
- 是 **運算式 (expression)**,所以可以直接寫在 JSX 裡。
- 常用來處理「二選一」的情況。
---
**(3) && 短路運算**
語法:
```jsx
{條件 && <元素A />}
```
- 如果 `條件 = true` → 顯示 `<元素A />`。
- 如果 `條件 = false` → React 忽略,不渲染任何東西。
- 適合「有就顯示、沒有就不顯示」。
---
**差異重點**
- **if** → 適合整段邏輯判斷(例如:載入中就直接 return,後面不渲染)。
- **`? :` 三元運算子** → 適合小範圍「二選一」。
- **`&&` 短路運算** → 適合「有就顯示,沒有就不顯示」。
:test_tube:
小測驗
1. 如果 isLoggedIn = false,上面這一行會顯示什麼?
2. 如果 isLoading = true,整個畫面會怎樣?
---
#### 5. 列表渲染進階
**為什麼需要 key?**
- React 會比對舊陣列與新陣列,決定哪些需要更新。
- **key 是判斷元素身份的依據**,必須唯一且穩定。
- key 不正確 → React 搞錯元素 → 畫面錯位或輸入內容消失。
**錯誤示範:用 index 當 key**
```jsx
{todos.map((todo, i) => <li key={i}>{todo}</li>)}
```
**正確示範:用唯一 id**
```jsx
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
```
**搭配排序與過濾**
```jsx
<ul>
{todos
.filter(todo => !todo.done)
.sort((a, b) => a.text.localeCompare(b.text))
.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
```
**React 相對於 JS 的優勢**
- 表單驗證:只新增錯誤提示,其他輸入框保持不變。
- 即時搜尋:只刪除不符合的項目,其他輸入框內容保留。
- 互動式表格:排序或刪除時,只更新有變動的 row,checkbox 與輸入值保持原狀。
**重點**
- key 是元素的「身分證字號」,必須唯一且穩定。
- React 只更新差異,不會整個清空再重建。
:test_tube:
小測驗
1. 為什麼 key 不能用 index 當 key?
---
#### 6. 回應 Event(Responding to Events)
**概念**
- 在 React,可以在 JSX 中綁定 **事件處理函式 (event handler)**。
- Event handler 會在互動發生時觸發(例如:點擊、輸入、hover)。
- event handler 本質上就是 **function**,通常命名為 `handleXxx`。
---
**基本範例**
```jsx
export default function Button() {
function handleClick() {
alert("You clicked me!");
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
```
**重點**
- `onClick={handleClick}` → 傳遞函式,而不是呼叫它。
- React 會在點擊時才執行 `handleClick`。
---
**Inline handler 寫法**
```jsx
<button onClick={() => alert("You clicked me!")}>
Click me
</button>
```
- 適合簡短的邏輯。
- 本質上是傳遞一個匿名函式。
---
**常見陷阱**
```jsx
// ❌ 錯誤:會在 render 時立即執行
<button onClick={handleClick()}>Click me</button>
// ✅ 正確:傳遞函式
<button onClick={handleClick}>Click me</button>
```
---
**使用 props 的 event handler**
```jsx
function AlertButton({ message, children }) {
return (
<button onClick={() => alert(message)}>
{children}
</button>
);
}
export default function Toolbar() {
return (
<div>
<AlertButton message="Playing!">Play Movie</AlertButton>
<AlertButton message="Uploading!">Upload Image</AlertButton>
</div>
);
}
```
- event handler 可以使用 component 的 props。
- 同一個元件可以因為不同 props 而有不同行為。
---
**父子傳遞 event handler**
```jsx
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
export default function Toolbar() {
return (
<div>
<Button onClick={() => alert("Playing!")}>Play Movie</Button>
<Button onClick={() => alert("Uploading!")}>Upload Image</Button>
</div>
);
}
```
- parent 可以把邏輯傳下去,child 只負責顯示樣式與結構。
---
**事件傳遞與停止**
```jsx
function Button({ onClick, children }) {
return (
<buttononClick={(e) => {
e.stopPropagation(); // 阻止冒泡
onClick();
}}
>
{children}
</button>
);
}
export default function Toolbar() {
return (
<divclassName="Toolbar"
onClick={() => alert("You clicked on the toolbar!")}
>
<Button onClick={() => alert("Playing!")}>Play Movie</Button>
<Button onClick={() => alert("Uploading!")}>Upload Image</Button>
</div>
);
}
```
- 點擊按鈕 → 只觸發按鈕自己的事件,不會冒泡到外層 `<div>`。
- `e.stopPropagation()` 阻止事件傳遞。
- `e.preventDefault()` 則是阻止瀏覽器的預設行為(例如表單送出)。
---
**重點整理**
- **event handler 要傳遞函式,而不是呼叫**。
- 可以寫在外部,也可以 inline。
- 可以存取 props,讓相同元件行為不同。
- 可以從 parent 把事件處理邏輯傳給 child。
- 可以用 `stopPropagation` 或 `preventDefault` 控制事件傳遞與預設行為。
---
🧪 小測驗
1. 下列哪個寫法是錯誤的?
```jsx
<button onClick={handleClick}>Click</button>
<button onClick={() => handleClick()}>Click</button>
<button onClick={handleClick()}>Click</button>
```
2. 點擊以下程式碼中的「Play Movie」按鈕會出現幾個 alert?
```jsx
<div onClick={() => alert("Toolbar clicked!")}>
<button onClick={() => alert("Playing!")}>Play Movie</button>
</div>
```
3. 要如何阻止表單送出時自動刷新頁面?
---
#### 7. 小專案 Demo:SurveyForm 問卷填報系統
**功能**
- 新增 / 刪除 回覆
- 搜尋 / 篩選 回覆
- 排序 (依名字升冪 / 降冪)
- 受控表單驗證(姓名必填)
**程式碼**
(沿用 TodoListV2,不再重貼)
---
#### 8. 課堂總結
- Hook 是 React 記住狀態的方式
- useState 的陷阱與進階用法
- 狀態隔離 vs 狀態共享
- 條件式 Rendering 的三種寫法
- 列表渲染與 key 的重要性
- 受控元件處理表單輸入
- 綜合練習:TodoList v2