⚛️ React 基礎:useState、Props 與事件處理
📌 函式元件(Function Components)
React 元件就是一個 JavaScript 函式,回傳 JSX 來描述 UI。
⚠️ 再次強調:React 元件不是原生 HTML,JSX 會被編譯成
React.createElement()呼叫。
// 最簡單的函式元件
function Welcome() {
return <h1>歡迎使用 React!</h1>;
}
// 箭頭函式寫法(也很常見)
const Welcome = () => <h1>歡迎使用 React!</h1>;
// 使用元件(像 HTML 標籤一樣)
function App() {
return (
<div>
<Welcome /> {/* 使用自訂元件 */}
<Welcome /> {/* 可以重複使用 */}
</div>
);
}
🔄 useState Hook — 狀態管理
useState 讓元件擁有「記憶」——能記住並更新資料。
import { useState } from 'react';
function Counter() {
// useState 回傳 [目前的值, 更新函式]
const [count, setCount] = useState(0); // 初始值 = 0
return (
<div>
<p>目前計數:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>歸零</button>
</div>
);
}
💡 為什麼不能直接
count = count + 1? React 只在呼叫setCount時才知道要重新渲染。 直接修改變數不會觸發 UI 更新——這是 React 與原生 JS 最大的差異之一。
物件與陣列狀態
function UserForm() {
const [user, setUser] = useState({ name: '', age: 0 });
// ⚠️ 必須展開原物件,React 靠「新參考」判斷是否更新
const updateName = (e) => {
setUser({ ...user, name: e.target.value }); // 展開運算子
};
return (
<input value={user.name} onChange={updateName} />
);
}
📦 Props — 父元件傳資料給子元件
Props 是元件之間溝通的方式,像函式的參數一樣。
// 子元件:接收 props
function UserCard({ name, age, isActive }) {
return (
<div className={`card ${isActive ? 'active' : ''}`}>
<h2>{name}</h2>
<p>年齡:{age}</p>
<span>{isActive ? '🟢 上線' : '🔴 離線'}</span>
</div>
);
}
// 父元件:傳遞 props
function App() {
return (
<div>
<UserCard name="小明" age={25} isActive={true} />
<UserCard name="小華" age={30} isActive={false} />
</div>
);
}
⚠️ Props 是唯讀的! 子元件不能修改收到的 props。 如果需要改變資料,應該由父元件透過回呼函式處理。
🖱️ 事件處理
React 事件用 camelCase 命名(onClick 而非 onclick),傳入的是函式而非字串。
function EventDemo() {
// 點擊事件
const handleClick = () => {
alert('按鈕被點擊了!');
};
// 輸入變更事件
const [text, setText] = useState('');
const handleChange = (e) => {
setText(e.target.value); // e.target.value 取得輸入值
};
// 表單送出事件
const handleSubmit = (e) => {
e.preventDefault(); // 阻止表單預設送出行為
console.log('提交的文字:', text);
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={handleChange}
placeholder="輸入文字..."
/>
<button type="submit">送出</button>
<button type="button" onClick={handleClick}>點我</button>
</form>
);
}
💡 React 事件 vs 原生 JS 事件:
- 原生:
element.addEventListener('click', handler)- React:
<button onClick={handler}>(宣告式,更直覺)- React 使用 合成事件(SyntheticEvent),跨瀏覽器一致。
🔀 條件渲染
function StatusMessage({ isLoggedIn, username }) {
// 方法一:三元運算子
return (
<div>
{isLoggedIn ? (
<p>歡迎回來,{username}!</p>
) : (
<p>請先登入</p>
)}
{/* 方法二:&& 短路求值 */}
{isLoggedIn && <button>登出</button>}
</div>
);
}
📋 列表渲染
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '學習 React', done: false },
{ id: 2, text: '寫元件', done: true },
{ id: 3, text: '部署應用', done: false },
]);
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};
return (
<ul>
{todos.map(todo => (
<li
key={todo.id} // ⚠️ key 是必要的!
onClick={() => toggleTodo(todo.id)}
style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
);
}
⚠️ key 的重要性: React 用
key來追蹤列表中的每個項目。沒有key或用index當key可能導致效能問題和 bug。
🛠️ 完整範例:計數器 + 待辦清單
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
const [input, setInput] = useState('');
const [todos, setTodos] = useState([]);
const addTodo = () => {
if (input.trim() === '') return;
setTodos([...todos, { id: Date.now(), text: input, done: false }]);
setInput(''); // 清空輸入
setCount(count + 1); // 計數器加 1
};
return (
<div style={{ padding: '20px' }}>
<h1>已新增 {count} 個待辦</h1>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addTodo()}
placeholder="輸入待辦事項..."
/>
<button onClick={addTodo}>新增</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}
✅ 本章重點
| 觀念 | 說明 |
|---|---|
| 函式元件 | 回傳 JSX 的函式,大寫開頭 |
| useState | 狀態管理 Hook,更新觸發重新渲染 |
| Props | 父傳子的唯讀資料 |
| 事件處理 | camelCase、傳入函式、合成事件 |
| 條件渲染 | 三元運算子、&& 短路 |
| 列表渲染 | .map() + key |