# React Hooks 深度解析與最佳實踐
React Hooks是React 16.8引入的革命性功能,它讓函數組件也能擁有狀態和生命週期。本文將深入探討Hooks的核心概念和實際應用。
## 1. Hooks的基本概念
Hooks是讓你在函數組件中"鉤入"React狀態和生命週期特性的函數。
### 為什麼需要Hooks?
- **解決類組件的複雜性**:避免this綁定、生命週期方法分散等問題
- **邏輯重用**:自定義Hook讓邏輯更容易在不同組件間共享
- **更好的程式碼組織**:相關邏輯可以放在一起
## 2. useState Hook
useState是最基礎的Hook,用於在函數組件中添加狀態。
jsx
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return (
<div>
<p>計數: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="輸入姓名"
/>
<p>你好, {name}!</p>
</div>
);
}
### useState的最佳實踐
jsx
// 1. 使用函數式更新避免依賴舊狀態
const [count, setCount] = useState(0);
// 好的做法
setCount(prevCount => prevCount + 1);
// 2. 複雜狀態使用useReducer
const [state, dispatch] = useReducer(reducer, initialState);
// 3. 避免在useState中放置計算結果
// 不好的做法
const [fullName, setFullName] = useState(firstName + ' ' + lastName);
// 好的做法
const fullName = firstName + ' ' + lastName;
## 3. useEffect Hook
useEffect用於處理副作用,如API調用、訂閱、手動DOM操作等。
jsx
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (error) {
console.error('獲取用戶資料失敗:', error);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // 依賴陣列
if (loading) return <div>載入中...</div>;
if (!user) return <div>用戶不存在</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
### useEffect的依賴陣列
jsx
// 1. 空依賴陣列 - 只在組件掛載時執行一次
useEffect(() => {
console.log('組件已掛載');
}, []);
// 2. 有依賴 - 當依賴變化時重新執行
useEffect(() => {
document.title = `新消息: ${messageCount}`;
}, [messageCount]);
// 3. 無依賴陣列 - 每次渲染都執行(通常不推薦)
useEffect(() => {
console.log('每次渲染都執行');
});
// 4. 清理函數
useEffect(() => {
const timer = setInterval(() => {
console.log('定時器執行');
}, 1000);
return () => {
clearInterval(timer); // 清理函數
};
}, []);
## 4. useContext Hook
useContext用於在組件樹中共享資料,避免props drilling。
jsx
import React, { createContext, useContext, useState } from 'react';
// 創建Context
const ThemeContext = createContext();
// Provider組件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
{children}
);
}
// 使用Context的組件
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff',
padding: '10px 20px',
border: '1px solid #ccc'
}}
>
切換主題
</button>
);
}
// 應用根組件
function App() {
return (
<div>
<h1>主題切換範例</h1>
</div>
);
}
## 5. 自定義Hooks
自定義Hook是重用狀態邏輯的強大工具。
jsx
// 自定義Hook: 使用localStorage
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 自定義Hook: API調用
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// 使用自定義Hook
function UserList() {
const { data: users, loading, error } = useApi('/api/users');
const [theme, setTheme] = useLocalStorage('theme', 'light');
if (loading) return <div>載入中...</div>;
if (error) return <div>錯誤: {error.message}</div>;
return (
<div>
<h2>用戶列表</h2>
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
## 6. useReducer Hook
useReducer適用於複雜狀態邏輯的場景。
jsx
import React, { useReducer } from 'react';
// 初始狀態
const initialState = {
count: 0,
step: 1
};
// Reducer函數
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.payload };
case 'reset':
return initialState;
default:
throw new Error('未知的action類型');
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div>
<h2>計數器: {state.count}</h2>
<div>
<button onClick={() => dispatch({ type: 'increment' })}>
增加
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
減少
</button>
<button onClick={() => dispatch({ type: 'reset' })}>
重置
</button>
</div>
<div>
<label>
步長:
<input
type="number"
value={state.step}
onChange={(e) => dispatch({
type: 'setStep',
payload: parseInt(e.target.value) || 1
})}
/>
</label>
</div>
</div>
);
}
## 7. 性能優化Hooks
### useMemo和useCallback
jsx
import React, { useState, useMemo, useCallback } from 'react';
function ExpensiveComponent({ items, filter }) {
// 使用useMemo緩存計算結果
const filteredItems = useMemo(() => {
console.log('重新計算過濾結果');
return items.filter(item => item.includes(filter));
}, [items, filter]);
// 使用useCallback緩存函數
const handleItemClick = useCallback((item) => {
console.log('點擊項目:', item);
}, []);
return (
<ul>
{filteredItems.map(item => (
<li key={item} onClick={() => handleItemClick(item)}>
{item}
</li>
))}
</ul>
);
}
function ParentComponent() {
const [items, setItems] = useState(['apple', 'banana', 'cherry']);
const [filter, setFilter] = useState('');
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="過濾項目"
/>
</div>
);
}
## 8. 常見陷阱和最佳實踐
### 1. 避免在useEffect中缺少依賴
jsx
// 錯誤的做法
function Example({ id }) {
const [data, setData] = useState(null);
useEffect(() => {
fetchData(id); // 缺少id依賴
}, []); // 這會導致ESLint警告
// 正確的做法
useEffect(() => {
fetchData(id);
}, [id]);
}
### 2. 避免無限循環
jsx
// 錯誤的做法 - 會導致無限循環
useEffect(() => {
setCount(count + 1);
}, [count]);
// 正確的做法
useEffect(() => {
setCount(prev => prev + 1);
}, []); // 只在掛載時執行一次
### 3. 正確使用清理函數
jsx
useEffect(() => {
const subscription = someAPI.subscribe();
return () => {
subscription.unsubscribe(); // 清理訂閱
};
}, []);
## 總結
React Hooks提供了:
1. **簡化的狀態管理**:useState和useReducer
2. **副作用處理**:useEffect
3. **上下文共享**:useContext
4. **性能優化**:useMemo和useCallback
5. **邏輯重用**:自定義Hooks
掌握這些Hooks的使用技巧,將大大提升React開發效率和程式碼品質。記住Hooks的規則:
- 只在函數組件頂層調用Hooks
- 只在React函數中調用Hooks
- 保持依賴陣列的正確性