cover

概念圖

Hooks 讓你在函式元件裡面做到以前只有 Class 元件才能做的事——而且程式碼少了一半。

先講結論

React Hooks 是 React 16.8 引入的 API。核心概念就是:useState 管狀態、useEffect 管副作用、useContext 管跨元件共享。這三個搞懂了,日常開發 90% 的場景都能應付。useMemouseCallback 是效能優化用的,不要過早使用。

useState:最基本的狀態管理

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

以前用 Class 元件要寫 this.statethis.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 不認。

兩條鐵規

  1. 只在最頂層呼叫 Hooks — 不要放在 if、for、巢狀函式裡面
  2. 只在 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% 的坑。


延伸閱讀

React 官方文件 - Hooks 前端使用哪些方法跟API互動 前端 MVP 設計原則