

什麼是 Hooks?
Hooks 是 React 16.8 引入的 API,讓你在函式元件中使用狀態和其他 React 特性,不需要寫 Class 元件。
// ❌ 以前:Class 元件
class Counter extends React.Component {
state = { count: 0 };
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
{this.state.count}
</button>
);
}
}
// ✅ 現在:函式元件 + Hooks
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}useState - 狀態管理
import { useState } from 'react';
function Example() {
// 宣告一個狀態變數 count,初始值為 0
const [count, setCount] = useState(0);
// 可以有多個 state
const [name, setName] = useState('');
const [items, setItems] = useState([]);
const [user, setUser] = useState({ name: '', age: 0 });
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(prev => prev + 1)}>+1 (函式更新)</button>
</div>
);
}更新物件/陣列狀態
// 更新物件 - 需要展開原有屬性
const [user, setUser] = useState({ name: '', age: 0 });
setUser({ ...user, name: 'John' });
setUser(prev => ({ ...prev, age: prev.age + 1 }));
// 更新陣列
const [items, setItems] = useState([]);
setItems([...items, newItem]); // 新增
setItems(items.filter(item => item.id !== id)); // 刪除
setItems(items.map(item =>
item.id === id ? { ...item, done: true } : item
)); // 更新特定項目useEffect - 副作用處理
處理「副作用」:資料請求、訂閱、DOM 操作等。
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 基本用法:每次渲染後執行
useEffect(() => {
console.log('元件渲染了');
});
// 加上依賴陣列:只在 userId 變化時執行
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
// 空陣列:只在掛載時執行一次
useEffect(() => {
console.log('元件掛載了');
}, []);
// 清理函式:元件卸載或依賴變化前執行
useEffect(() => {
const subscription = api.subscribe(userId);
return () => {
subscription.unsubscribe(); // 清理
};
}, [userId]);
if (loading) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}useEffect 執行時機
┌─────────────────────────────────────────────────┐
│ useEffect(() => { ... }) │
│ → 每次渲染後執行 │
├─────────────────────────────────────────────────┤
│ useEffect(() => { ... }, []) │
│ → 只在掛載時執行一次 │
├─────────────────────────────────────────────────┤
│ useEffect(() => { ... }, [dep1, dep2]) │
│ → 掛載時 + dep1 或 dep2 變化時執行 │
├─────────────────────────────────────────────────┤
│ useEffect(() => { │
│ return () => { /* cleanup */ } │
│ }, [dep]) │
│ → dep 變化前 或 卸載時 執行 cleanup │
└─────────────────────────────────────────────────┘
useContext - 跨元件共享狀態
import { createContext, useContext, useState } from 'react';
// 1. 建立 Context
const ThemeContext = createContext();
// 2. 建立 Provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. 使用 Context
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{ background: theme === 'light' ? '#fff' : '#333' }}
>
切換主題
</button>
);
}
// 4. 在 App 中使用
function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}useRef - 參照和持久值
import { useRef, useEffect } from 'react';
function TextInput() {
// 1. 參照 DOM 元素
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // 自動聚焦
}, []);
return <input ref={inputRef} />;
}
function Timer() {
// 2. 儲存不需要觸發重新渲染的值
const intervalRef = useRef(null);
const countRef = useRef(0); // 不會觸發重新渲染
const startTimer = () => {
intervalRef.current = setInterval(() => {
countRef.current += 1;
console.log(countRef.current);
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
};
return (
<>
<button onClick={startTimer}>開始</button>
<button onClick={stopTimer}>停止</button>
</>
);
}useMemo - 快取計算結果
避免昂貴的計算在每次渲染時重複執行。
import { useMemo, useState } from 'react';
function ExpensiveComponent({ items, filter }) {
// ❌ 每次渲染都會重新計算
const filteredItems = items.filter(item => item.includes(filter));
// ✅ 只在 items 或 filter 變化時才重新計算
const filteredItems = useMemo(() => {
console.log('計算中...');
return items.filter(item => item.includes(filter));
}, [items, filter]);
return (
<ul>
{filteredItems.map(item => <li key={item}>{item}</li>)}
</ul>
);
}useCallback - 快取函式
避免子元件因為函式引用改變而不必要地重新渲染。
import { useCallback, useState, memo } from 'react';
// 用 memo 包裝的子元件只在 props 改變時重新渲染
const Button = memo(({ onClick, children }) => {
console.log('Button 渲染');
return <button onClick={onClick}>{children}</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// ❌ 每次渲染都建立新函式,導致 Button 重新渲染
const handleClick = () => setCount(c => c + 1);
// ✅ 快取函式,Button 不會因為 name 改變而重新渲染
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<>
<input value={name} onChange={e => setName(e.target.value)} />
<Button onClick={handleClick}>Count: {count}</Button>
</>
);
}自訂 Hook
將邏輯封裝成可重用的 Hook。
// useLocalStorage - 同步 localStorage
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 [theme, setTheme] = useLocalStorage('theme', 'light');
// ...
}// useFetch - 資料請求
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
// 使用
function UserList() {
const { data: users, loading, error } = useFetch('/api/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}Hooks 規則
- 只在最頂層呼叫 Hooks - 不要在迴圈、條件式或巢狀函式中呼叫
- 只在 React 函式中呼叫 Hooks - 不要在一般 JavaScript 函式中呼叫
// ❌ 錯誤
if (condition) {
const [value, setValue] = useState(0);
}
// ✅ 正確
const [value, setValue] = useState(0);
if (condition) {
// 使用 value
}