技術 6 分鐘閱讀

React Hooks 實戰技巧:從基礎到進階應用

React Hooks 實戰技巧:從基礎到進階應用

前言

React Hooks 從 16.8 版推出到現在,已經成為寫 React 的標準方式。我在接案的過程中累積了不少實用技巧,這篇文章整理出來分享給大家。

useState 的進階用法

使用函式初始化

如果初始值需要經過複雜計算,可以用函式形式避免每次渲染都重新計算:

// 不好的做法:每次渲染都會呼叫 expensiveCalculation
const [data, setData] = useState(expensiveCalculation());

// 好的做法:只在首次渲染時計算
const [data, setData] = useState(() => expensiveCalculation());

物件狀態的更新

更新物件狀態時,記得展開原有的屬性:

const [form, setForm] = useState({
  name: '',
  email: '',
  phone: '',
});

const handleChange = (field: string, value: string) => {
  setForm(prev => ({ ...prev, [field]: value }));
};

這在表單處理時非常常見,我幾乎每個專案都會用到。

useEffect 的正確使用

清除副作用

很多人忽略了 cleanup function,這在處理訂閱或計時器時特別重要:

useEffect(() => {
  const timer = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);

  // 清除計時器,避免記憶體洩漏
  return () => clearInterval(timer);
}, []);

避免不必要的 API 呼叫

使用 AbortController 來取消未完成的請求:

useEffect(() => {
  const controller = new AbortController();

  const fetchData = async () => {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        signal: controller.signal,
      });
      const data = await response.json();
      setUser(data);
    } catch (error) {
      if (error.name !== 'AbortError') {
        setError(error.message);
      }
    }
  };

  fetchData();

  return () => controller.abort();
}, [userId]);

useCallback 與 useMemo

什麼時候該用 useCallback

不是所有函式都需要用 useCallback 包起來。只有在以下情況才有意義:

  1. 函式作為 props 傳給使用 React.memo 的子元件
  2. 函式是其他 Hook 的依賴項
// 有意義的使用
const handleSubmit = useCallback(async (formData: FormData) => {
  await api.createOrder(formData);
  router.push('/orders');
}, [router]);

// 傳給 memo 化的子元件
<MemoizedForm onSubmit={handleSubmit} />

useMemo 的實際應用

過濾和排序是 useMemo 最常見的使用場景:

const filteredProducts = useMemo(() => {
  return products
    .filter(p => p.category === selectedCategory)
    .sort((a, b) => a.price - b.price);
}, [products, selectedCategory]);

自定義 Hook:把邏輯抽出來

useLocalStorage

這個 Hook 我幾乎每個專案都會用:

function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = (value: T | ((val: T) => T)) => {
    const valueToStore = value instanceof Function
      ? value(storedValue)
      : value;
    setStoredValue(valueToStore);
    window.localStorage.setItem(key, JSON.stringify(valueToStore));
  };

  return [storedValue, setValue] as const;
}

使用方式:

const [theme, setTheme] = useLocalStorage('theme', 'light');

useDebounce

搜尋輸入時非常好用的 Hook:

function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

搭配搜尋功能使用:

const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 300);

useEffect(() => {
  if (debouncedSearch) {
    fetchSearchResults(debouncedSearch);
  }
}, [debouncedSearch]);

useRef 的妙用

除了存取 DOM 元素,useRef 也能用來保存不需要觸發重新渲染的值:

const renderCount = useRef(0);

useEffect(() => {
  renderCount.current += 1;
  console.log(`元件已渲染 ${renderCount.current} 次`);
});

在計時器或動畫中保存最新的 callback:

const callbackRef = useRef(callback);

useEffect(() => {
  callbackRef.current = callback;
});

useEffect(() => {
  const tick = () => callbackRef.current();
  const id = setInterval(tick, delay);
  return () => clearInterval(id);
}, [delay]);

實戰心得

在多個接案專案中,我歸納出幾個使用 Hooks 的原則:

  1. 不要過度優化:先寫出正確的程式碼,有效能問題再優化
  2. 自定義 Hook 命名以 use 開頭:這是慣例,也讓 ESLint 規則能正確檢查
  3. 保持 Hook 的純粹性:一個 Hook 只做一件事
  4. 善用 TypeScript:型別推導能大幅減少 Hook 使用上的錯誤

結語

React Hooks 讓函式元件變得更強大也更靈活。掌握好這些技巧,能讓你的程式碼更乾淨、更好維護。如果你也是接案開發者,好的程式碼品質就是你最好的名片。

#React #Hooks #JavaScript #前端開發
Thousand Lab

Thousand Lab