技術 6 分鐘閱讀
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 包起來。只有在以下情況才有意義:
- 函式作為 props 傳給使用
React.memo的子元件 - 函式是其他 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 的原則:
- 不要過度優化:先寫出正確的程式碼,有效能問題再優化
- 自定義 Hook 命名以 use 開頭:這是慣例,也讓 ESLint 規則能正確檢查
- 保持 Hook 的純粹性:一個 Hook 只做一件事
- 善用 TypeScript:型別推導能大幅減少 Hook 使用上的錯誤
結語
React Hooks 讓函式元件變得更強大也更靈活。掌握好這些技巧,能讓你的程式碼更乾淨、更好維護。如果你也是接案開發者,好的程式碼品質就是你最好的名片。
#React #Hooks #JavaScript #前端開發