

Hooks 讓你在函式元件裡面做到以前只有 Class 元件才能做的事——而且程式碼少了一半。
先講結論
React Hooks 是 React 16.8 引入的 API。核心概念就是:useState 管狀態、useEffect 管副作用、useContext 管跨元件共享。這三個搞懂了,日常開發 90% 的場景都能應付。useMemo 和 useCallback 是效能優化用的,不要過早使用。
useState:最基本的狀態管理
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}以前用 Class 元件要寫 this.state、this.setState、還要綁 this——光是搞 this 的指向就夠你頭痛。Hooks 直接解決了這個問題。
更新物件和陣列的陷阱
React 的狀態更新是替換不是修改。你不能直接改原物件,要展開後建立新的:
const [user, setUser] = useState({ name: '', age: 0 });
// 錯:直接改原物件,React 不會重新渲染
user.name = 'John';
// 對:建立新物件
setUser({ ...user, name: 'John' });
// 陣列也一樣
const [items, setItems] = useState([]);
setItems([...items, newItem]); // 新增
setItems(items.filter(i => i.id !== id)); // 刪除這是最常見的 React 新手坑。你明明改了 state,畫面卻沒更新——因為你改的是同一個 reference,React 認為「沒變啊」。
useEffect:處理副作用
「副作用」就是 render 以外的事:打 API、訂閱事件、操作 DOM。
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// 只在 userId 變化時打 API
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
// 訂閱 + 清理
useEffect(() => {
const subscription = api.subscribe(userId);
return () => subscription.unsubscribe(); // 清理!
}, [userId]);
return user ? <h1>{user.name}</h1> : <p>Loading...</p>;
}依賴陣列怎麼看
useEffect(() => { ... }) → 每次渲染後都執行
useEffect(() => { ... }, []) → 只在掛載時執行一次
useEffect(() => { ... }, [a, b]) → a 或 b 變化時執行
useEffect(() => {
return () => { /* cleanup */ } → 卸載或依賴變化前執行清理
}, [dep])
忘了加依賴陣列,你的 API 會瘋狂地無限打。忘了寫 cleanup,你的事件監聽器會累積到記憶體爆掉。這兩種我都幹過。
useContext:跨元件傳值不用 prop drilling
props 穿三層以上就該考慮 Context 了:
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 任何子元件都能直接拿到
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
目前:{theme}
</button>;
}但 Context 不是萬能的——它的 value 一變,所有 consumer 都會重新渲染。如果你發現 Context 裝了太多東西導致效能問題,那就該考慮用 Zustand 或 Jotai 了。
useRef:不觸發渲染的儲存空間
兩個用途:參照 DOM 元素、儲存不需要觸發渲染的值。
function TextInput() {
const inputRef = useRef(null);
useEffect(() => { inputRef.current.focus(); }, []);
return <input ref={inputRef} />;
}
function Timer() {
const intervalRef = useRef(null);
const startTimer = () => {
intervalRef.current = setInterval(() => console.log('tick'), 1000);
};
const stopTimer = () => clearInterval(intervalRef.current);
return <>
<button onClick={startTimer}>開始</button>
<button onClick={stopTimer}>停止</button>
</>;
}useRef 改值不會觸發重新渲染——這是它跟 useState 最大的差異。適合存 timer ID、上一次的值、或任何「需要保存但不影響畫面」的東西。
useMemo / useCallback:不要過早使用
// useMemo:快取計算結果
const filtered = useMemo(() => {
return items.filter(item => item.includes(filter));
}, [items, filter]);
// useCallback:快取函式引用
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);什麼時候該用? 當你用 React DevTools 的 Profiler 確認某個計算或渲染真的造成效能問題的時候。不要每個 function 都包 useCallback、每個變數都包 useMemo——過早優化反而讓程式碼更難讀,而且 memo 本身也有成本。
自訂 Hook:邏輯復用的正確方式
把重複的邏輯抽出來:
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];
}
// 用法跟 useState 一樣
const [theme, setTheme] = useLocalStorage('theme', 'light');自訂 Hook 就是一個用了其他 Hook 的函式。名字必須以 use 開頭,否則 React 不認。
兩條鐵規
- 只在最頂層呼叫 Hooks — 不要放在 if、for、巢狀函式裡面
- 只在 React 函式中呼叫 Hooks — 不要在一般函式裡用
// 錯:條件式呼叫
if (condition) {
const [value, setValue] = useState(0); // React 追蹤不到
}
// 對:在頂層呼叫,邏輯放後面
const [value, setValue] = useState(0);
if (condition) { /* 用 value 做事 */ }React 用呼叫順序來追蹤每個 Hook 對應的狀態。如果順序亂了,state 就會錯位——這個 bug 會讓你懷疑人生。
Hooks 讓 React 從「物件導向的複雜儀式」變成「函式的簡單組合」。但簡單不代表不需要理解——搞懂依賴陣列和 closure,你就征服了 Hooks 80% 的坑。