React Hooks 深入解析:现代 React 开发的核心技术
React Hooks 是 React 16.8 引入的革命性特性,让函数组件拥有了状态管理和生命周期的能力。本文将深入解析 Hooks 的原理、用法和最佳实践。
2025年9月18日
DocsLib Team
ReactHooksJavaScript前端开发状态管理
React Hooks 深入解析:现代 React 开发的核心技术
React Hooks 简介
React Hooks 是 React 16.8 版本引入的新特性,它允许你在函数组件中使用状态和其他 React 特性。Hooks 的出现彻底改变了 React 开发的方式,使得函数组件能够完全替代类组件。
Hooks 的优势
- 简化组件逻辑:减少样板代码,提高代码可读性
- 更好的逻辑复用:通过自定义 Hooks 实现逻辑共享
- 更容易测试:函数组件比类组件更容易测试
- 更好的性能优化:避免不必要的重新渲染
- 更符合函数式编程:组件即函数的理念
- 更小的包体积:函数组件通常比类组件更轻量
基础 Hooks
useState:状态管理
useState
是最基础也是最常用的 Hook,用于在函数组件中添加状态。
import React, { useState } from 'react';
// 基础用法
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
<button onClick={() => setCount(count - 1)}>
减少
</button>
<button onClick={() => setCount(0)}>
重置
</button>
</div>
);
}
// 复杂状态管理
function UserProfile() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
const updateUser = (field, value) => {
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
return (
<form>
<input
type="text"
placeholder="姓名"
value={user.name}
onChange={(e) => updateUser('name', e.target.value)}
/>
<input
type="email"
placeholder="邮箱"
value={user.email}
onChange={(e) => updateUser('email', e.target.value)}
/>
<input
type="number"
placeholder="年龄"
value={user.age}
onChange={(e) => updateUser('age', parseInt(e.target.value))}
/>
<div>
<h3>用户信息预览:</h3>
<p>姓名: {user.name}</p>
<p>邮箱: {user.email}</p>
<p>年龄: {user.age}</p>
</div>
</form>
);
}
// 函数式更新
function TodoList() {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
const addTodo = () => {
if (inputValue.trim()) {
setTodos(prevTodos => [
...prevTodos,
{
id: Date.now(),
text: inputValue,
completed: false
}
]);
setInputValue('');
}
};
const toggleTodo = (id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
};
const deleteTodo = (id) => {
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
};
return (
<div>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="添加新任务"
/>
<button onClick={addTodo}>添加</button>
</div>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
useEffect:副作用处理
useEffect
用于处理副作用,如数据获取、订阅、手动 DOM 操作等。
import React, { useState, useEffect } from 'react';
// 基础用法
function DocumentTitle() {
const [count, setCount] = useState(0);
// 每次渲染后执行
useEffect(() => {
document.title = `计数: ${count}`;
});
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加计数
</button>
</div>
);
}
// 依赖数组控制执行时机
function UserData({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 只在 userId 变化时执行
const fetchUser = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('用户数据获取失败');
}
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (userId) {
fetchUser();
}
}, [userId]); // 依赖数组
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
if (!user) return <div>未找到用户</div>;
return (
<div>
<h2>{user.name}</h2>
<p>邮箱: {user.email}</p>
<p>注册时间: {user.createdAt}</p>
</div>
);
}
// 清理副作用
function Timer() {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
useEffect(() => {
let intervalId;
if (isRunning) {
intervalId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
}
// 清理函数
return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
}, [isRunning]);
const handleStart = () => setIsRunning(true);
const handleStop = () => setIsRunning(false);
const handleReset = () => {
setIsRunning(false);
setSeconds(0);
};
return (
<div>
<h2>计时器: {seconds} 秒</h2>
<button onClick={handleStart} disabled={isRunning}>
开始
</button>
<button onClick={handleStop} disabled={!isRunning}>
停止
</button>
<button onClick={handleReset}>
重置
</button>
</div>
);
}
// 窗口大小监听
function WindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
// 清理事件监听器
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // 空依赖数组,只在组件挂载和卸载时执行
return (
<div>
<p>窗口宽度: {windowSize.width}px</p>
<p>窗口高度: {windowSize.height}px</p>
</div>
);
}
useContext:上下文消费
useContext
用于消费 React Context,避免组件树中的 props 层层传递。
import React, { createContext, useContext, useState } from 'react';
// 创建主题上下文
const ThemeContext = createContext();
// 主题提供者组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
const themeValue = {
theme,
toggleTheme,
colors: {
light: {
background: '#ffffff',
text: '#000000',
primary: '#007bff'
},
dark: {
background: '#1a1a1a',
text: '#ffffff',
primary: '#4dabf7'
}
}
};
return (
<ThemeContext.Provider value={themeValue}>
{children}
</ThemeContext.Provider>
);
}
// 使用主题的组件
function ThemedButton({ children, onClick }) {
const { theme, colors } = useContext(ThemeContext);
const currentColors = colors[theme];
return (
<button
onClick={onClick}
style={{
backgroundColor: currentColors.primary,
color: currentColors.background,
border: 'none',
padding: '10px 20px',
borderRadius: '5px',
cursor: 'pointer'
}}
>
{children}
</button>
);
}
// 主题切换器
function ThemeToggler() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<ThemedButton onClick={toggleTheme}>
切换到 {theme === 'light' ? '深色' : '浅色'} 主题
</ThemedButton>
);
}
// 主应用组件
function ThemedApp() {
const { theme, colors } = useContext(ThemeContext);
const currentColors = colors[theme];
return (
<div
style={{
backgroundColor: currentColors.background,
color: currentColors.text,
minHeight: '100vh',
padding: '20px'
}}
>
<h1>主题切换示例</h1>
<p>当前主题: {theme}</p>
<ThemeToggler />
</div>
);
}
// 用户认证上下文示例
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 模拟登录
const login = async (email, password) => {
setLoading(true);
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000));
const userData = {
id: 1,
email,
name: '用户名',
avatar: '/default-avatar.png'
};
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
} catch (error) {
throw new Error('登录失败');
} finally {
setLoading(false);
}
};
// 登出
const logout = () => {
setUser(null);
localStorage.removeItem('user');
};
// 检查本地存储的用户信息
useEffect(() => {
const savedUser = localStorage.getItem('user');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
setLoading(false);
}, []);
const value = {
user,
login,
logout,
loading,
isAuthenticated: !!user
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// 使用认证上下文的组件
function LoginForm() {
const { login, loading } = useContext(AuthContext);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
setError('');
await login(email, password);
} catch (err) {
setError(err.message);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="email"
placeholder="邮箱"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{error && <div style={{ color: 'red' }}>{error}</div>}
<button type="submit" disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
);
}
function UserProfile() {
const { user, logout } = useContext(AuthContext);
return (
<div>
<h2>欢迎, {user.name}!</h2>
<p>邮箱: {user.email}</p>
<button onClick={logout}>登出</button>
</div>
);
}
高级 Hooks
useReducer:复杂状态管理
useReducer
适用于复杂的状态逻辑,特别是当状态更新逻辑复杂或下一个状态依赖于之前的状态时。
import React, { useReducer } from 'react';
// 购物车状态管理
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
const existingItem = state.items.find(item => item.id === action.payload.id);
if (existingItem) {
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }]
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: Math.max(0, action.payload.quantity) }
: item
).filter(item => item.quantity > 0)
};
case 'CLEAR_CART':
return {
...state,
items: []
};
case 'APPLY_DISCOUNT':
return {
...state,
discount: action.payload
};
default:
return state;
}
};
function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, {
items: [],
discount: 0
});
// 计算总价
const total = cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const discountedTotal = total * (1 - cart.discount);
const addItem = (product) => {
dispatch({ type: 'ADD_ITEM', payload: product });
};
const removeItem = (id) => {
dispatch({ type: 'REMOVE_ITEM', payload: id });
};
const updateQuantity = (id, quantity) => {
dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } });
};
const clearCart = () => {
dispatch({ type: 'CLEAR_CART' });
};
const applyDiscount = (discount) => {
dispatch({ type: 'APPLY_DISCOUNT', payload: discount });
};
// 示例商品
const products = [
{ id: 1, name: 'iPhone 14', price: 999 },
{ id: 2, name: 'MacBook Pro', price: 1999 },
{ id: 3, name: 'AirPods Pro', price: 249 }
];
return (
<div>
<h2>购物车</h2>
{/* 商品列表 */}
<div>
<h3>商品</h3>
{products.map(product => (
<div key={product.id} style={{ margin: '10px 0' }}>
<span>{product.name} - ${product.price}</span>
<button onClick={() => addItem(product)}>添加到购物车</button>
</div>
))}
</div>
{/* 购物车内容 */}
<div>
<h3>购物车内容</h3>
{cart.items.length === 0 ? (
<p>购物车为空</p>
) : (
<>
{cart.items.map(item => (
<div key={item.id} style={{ margin: '10px 0' }}>
<span>{item.name} - ${item.price}</span>
<input
type="number"
value={item.quantity}
onChange={(e) => updateQuantity(item.id, parseInt(e.target.value))}
min="0"
style={{ width: '60px', margin: '0 10px' }}
/>
<span>小计: ${(item.price * item.quantity).toFixed(2)}</span>
<button onClick={() => removeItem(item.id)}>删除</button>
</div>
))}
<div style={{ marginTop: '20px' }}>
<p>原价: ${total.toFixed(2)}</p>
{cart.discount > 0 && (
<p>折扣: {(cart.discount * 100).toFixed(0)}%</p>
)}
<p><strong>总计: ${discountedTotal.toFixed(2)}</strong></p>
<button onClick={() => applyDiscount(0.1)}>应用10%折扣</button>
<button onClick={() => applyDiscount(0)}>移除折扣</button>
<button onClick={clearCart}>清空购物车</button>
</div>
</>
)}
</div>
</div>
);
}
// 表单状态管理
const formReducer = (state, action) => {
switch (action.type) {
case 'SET_FIELD':
return {
...state,
values: {
...state.values,
[action.field]: action.value
},
errors: {
...state.errors,
[action.field]: ''
}
};
case 'SET_ERROR':
return {
...state,
errors: {
...state.errors,
[action.field]: action.error
}
};
case 'SET_LOADING':
return {
...state,
loading: action.loading
};
case 'RESET_FORM':
return action.initialState;
default:
return state;
}
};
function ContactForm() {
const initialState = {
values: {
name: '',
email: '',
message: ''
},
errors: {},
loading: false
};
const [form, dispatch] = useReducer(formReducer, initialState);
const setField = (field, value) => {
dispatch({ type: 'SET_FIELD', field, value });
};
const setError = (field, error) => {
dispatch({ type: 'SET_ERROR', field, error });
};
const validateForm = () => {
let isValid = true;
if (!form.values.name.trim()) {
setError('name', '姓名不能为空');
isValid = false;
}
if (!form.values.email.trim()) {
setError('email', '邮箱不能为空');
isValid = false;
} else if (!/\S+@\S+\.\S+/.test(form.values.email)) {
setError('email', '邮箱格式不正确');
isValid = false;
}
if (!form.values.message.trim()) {
setError('message', '消息不能为空');
isValid = false;
}
return isValid;
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!validateForm()) {
return;
}
dispatch({ type: 'SET_LOADING', loading: true });
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 2000));
alert('表单提交成功!');
dispatch({ type: 'RESET_FORM', initialState });
} catch (error) {
alert('提交失败,请重试');
} finally {
dispatch({ type: 'SET_LOADING', loading: false });
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>姓名:</label>
<input
type="text"
value={form.values.name}
onChange={(e) => setField('name', e.target.value)}
/>
{form.errors.name && <span style={{ color: 'red' }}>{form.errors.name}</span>}
</div>
<div>
<label>邮箱:</label>
<input
type="email"
value={form.values.email}
onChange={(e) => setField('email', e.target.value)}
/>
{form.errors.email && <span style={{ color: 'red' }}>{form.errors.email}</span>}
</div>
<div>
<label>消息:</label>
<textarea
value={form.values.message}
onChange={(e) => setField('message', e.target.value)}
rows={4}
/>
{form.errors.message && <span style={{ color: 'red' }}>{form.errors.message}</span>}
</div>
<button type="submit" disabled={form.loading}>
{form.loading ? '提交中...' : '提交'}
</button>
</form>
);
}
useMemo 和 useCallback:性能优化
这两个 Hook 用于优化性能,避免不必要的计算和重新渲染。
import React, { useState, useMemo, useCallback, memo } from 'react';
// useMemo 示例:昂贵的计算
function ExpensiveCalculation({ numbers }) {
const [multiplier, setMultiplier] = useState(1);
// 使用 useMemo 缓存昂贵的计算结果
const expensiveValue = useMemo(() => {
console.log('执行昂贵的计算...');
return numbers.reduce((sum, num) => sum + num * multiplier, 0);
}, [numbers, multiplier]); // 只有当 numbers 或 multiplier 变化时才重新计算
// 不使用 useMemo 的对比
const normalValue = numbers.reduce((sum, num) => sum + num * multiplier, 0);
return (
<div>
<h3>性能优化示例</h3>
<p>乘数: {multiplier}</p>
<p>优化后的结果: {expensiveValue}</p>
<p>普通计算结果: {normalValue}</p>
<button onClick={() => setMultiplier(multiplier + 1)}>
增加乘数
</button>
</div>
);
}
// useCallback 示例:避免子组件不必要的重新渲染
const ChildComponent = memo(({ onButtonClick, value }) => {
console.log('ChildComponent 重新渲染');
return (
<div>
<p>子组件值: {value}</p>
<button onClick={onButtonClick}>点击我</button>
</div>
);
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(0);
// 使用 useCallback 缓存函数
const handleButtonClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // 空依赖数组,函数永远不会改变
// 不使用 useCallback 的对比
const handleButtonClickNormal = () => {
setCount(prevCount => prevCount + 1);
};
return (
<div>
<h3>useCallback 示例</h3>
<p>父组件计数: {count}</p>
<p>其他状态: {otherState}</p>
<button onClick={() => setOtherState(otherState + 1)}>
改变其他状态
</button>
<ChildComponent
onButtonClick={handleButtonClick}
value={count}
/>
</div>
);
}
// 复杂的 useMemo 和 useCallback 组合示例
function DataTable({ data, filters }) {
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
const [selectedItems, setSelectedItems] = useState(new Set());
// 使用 useMemo 缓存过滤和排序后的数据
const processedData = useMemo(() => {
console.log('处理数据...');
// 过滤数据
let filtered = data.filter(item => {
return Object.entries(filters).every(([key, value]) => {
if (!value) return true;
return item[key].toString().toLowerCase().includes(value.toLowerCase());
});
});
// 排序数据
if (sortConfig.key) {
filtered.sort((a, b) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === 'asc' ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === 'asc' ? 1 : -1;
}
return 0;
});
}
return filtered;
}, [data, filters, sortConfig]);
// 使用 useCallback 缓存事件处理函数
const handleSort = useCallback((key) => {
setSortConfig(prevConfig => ({
key,
direction: prevConfig.key === key && prevConfig.direction === 'asc' ? 'desc' : 'asc'
}));
}, []);
const handleSelectItem = useCallback((id) => {
setSelectedItems(prevSelected => {
const newSelected = new Set(prevSelected);
if (newSelected.has(id)) {
newSelected.delete(id);
} else {
newSelected.add(id);
}
return newSelected;
});
}, []);
const handleSelectAll = useCallback(() => {
if (selectedItems.size === processedData.length) {
setSelectedItems(new Set());
} else {
setSelectedItems(new Set(processedData.map(item => item.id)));
}
}, [processedData, selectedItems.size]);
// 使用 useMemo 缓存统计信息
const statistics = useMemo(() => {
return {
total: processedData.length,
selected: selectedItems.size,
averageAge: processedData.reduce((sum, item) => sum + item.age, 0) / processedData.length || 0
};
}, [processedData, selectedItems.size]);
return (
<div>
<div>
<h3>数据统计</h3>
<p>总数: {statistics.total}</p>
<p>已选择: {statistics.selected}</p>
<p>平均年龄: {statistics.averageAge.toFixed(1)}</p>
</div>
<table>
<thead>
<tr>
<th>
<input
type="checkbox"
checked={selectedItems.size === processedData.length && processedData.length > 0}
onChange={handleSelectAll}
/>
</th>
<th onClick={() => handleSort('name')} style={{ cursor: 'pointer' }}>
姓名 {sortConfig.key === 'name' && (sortConfig.direction === 'asc' ? '↑' : '↓')}
</th>
<th onClick={() => handleSort('age')} style={{ cursor: 'pointer' }}>
年龄 {sortConfig.key === 'age' && (sortConfig.direction === 'asc' ? '↑' : '↓')}
</th>
<th onClick={() => handleSort('email')} style={{ cursor: 'pointer' }}>
邮箱 {sortConfig.key === 'email' && (sortConfig.direction === 'asc' ? '↑' : '↓')}
</th>
</tr>
</thead>
<tbody>
{processedData.map(item => (
<tr key={item.id}>
<td>
<input
type="checkbox"
checked={selectedItems.has(item.id)}
onChange={() => handleSelectItem(item.id)}
/>
</td>
<td>{item.name}</td>
<td>{item.age}</td>
<td>{item.email}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
自定义 Hooks
自定义 Hooks 是 React Hooks 最强大的特性之一,允许你提取组件逻辑到可重用的函数中。
基础自定义 Hooks
import { useState, useEffect, useCallback, useRef } from 'react';
// 本地存储 Hook
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 reading localStorage key "${key}":`, error);
return initialValue;
}
});
// 设置值的函数
const setValue = useCallback((value) => {
try {
// 允许值是一个函数,以便我们有与 useState 相同的 API
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
}, [key, storedValue]);
return [storedValue, setValue];
}
// 使用本地存储 Hook 的组件
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'zh-CN');
const [notifications, setNotifications] = useLocalStorage('notifications', true);
return (
<div>
<h3>设置</h3>
<div>
<label>主题:</label>
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
</div>
<div>
<label>语言:</label>
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="zh-CN">中文</option>
<option value="en-US">English</option>
</select>
</div>
<div>
<label>
<input
type="checkbox"
checked={notifications}
onChange={(e) => setNotifications(e.target.checked)}
/>
启用通知
</label>
</div>
<div>
<h4>当前设置:</h4>
<p>主题: {theme}</p>
<p>语言: {language}</p>
<p>通知: {notifications ? '开启' : '关闭'}</p>
</div>
</div>
);
}
// 防抖 Hook
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// 使用防抖 Hook 的搜索组件
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
setLoading(true);
// 模拟 API 调用
setTimeout(() => {
const mockResults = [
`结果 1 for "${debouncedSearchTerm}"`,
`结果 2 for "${debouncedSearchTerm}"`,
`结果 3 for "${debouncedSearchTerm}"`
];
setResults(mockResults);
setLoading(false);
}, 1000);
} else {
setResults([]);
}
}, [debouncedSearchTerm]);
return (
<div>
<input
type="text"
placeholder="搜索..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
{loading && <p>搜索中...</p>}
<ul>
{results.map((result, index) => (
<li key={index}>{result}</li>
))}
</ul>
</div>
);
}
// 网络状态 Hook
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
// 使用网络状态 Hook 的组件
function NetworkStatus() {
const isOnline = useOnlineStatus();
return (
<div style={{
padding: '10px',
backgroundColor: isOnline ? '#d4edda' : '#f8d7da',
color: isOnline ? '#155724' : '#721c24',
border: `1px solid ${isOnline ? '#c3e6cb' : '#f5c6cb'}`,
borderRadius: '4px'
}}>
{isOnline ? '🟢 在线' : '🔴 离线'}
</div>
);
}
// 鼠标位置 Hook
function useMousePosition() {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setMousePosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return mousePosition;
}
// 使用鼠标位置 Hook 的组件
function MouseTracker() {
const { x, y } = useMousePosition();
return (
<div>
<h3>鼠标追踪器</h3>
<p>鼠标位置: ({x}, {y})</p>
<div
style={{
position: 'fixed',
top: y - 10,
left: x - 10,
width: '20px',
height: '20px',
backgroundColor: 'red',
borderRadius: '50%',
pointerEvents: 'none',
zIndex: 9999
}}
/>
</div>
);
}
高级自定义 Hooks
// 数据获取 Hook
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const abortControllerRef = useRef(null);
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
// 取消之前的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// 创建新的 AbortController
abortControllerRef.current = new AbortController();
const response = await fetch(url, {
...options,
signal: abortControllerRef.current.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
}, [url, options]);
useEffect(() => {
fetchData();
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [fetchData]);
const refetch = useCallback(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch };
}
// 使用数据获取 Hook 的组件
function UserList() {
const { data: users, loading, error, refetch } = useFetch('/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<h3>用户列表</h3>
<button onClick={refetch}>刷新</button>
<ul>
{users?.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
</div>
);
}
// 表单验证 Hook
function useFormValidation(initialValues, validationRules) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const setValue = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
// 如果字段已经被触摸过,立即验证
if (touched[name]) {
validateField(name, value);
}
}, [touched]);
const setTouched = useCallback((name) => {
setTouched(prev => ({ ...prev, [name]: true }));
validateField(name, values[name]);
}, [values]);
const validateField = useCallback((name, value) => {
const rule = validationRules[name];
if (!rule) return;
let error = '';
if (rule.required && (!value || value.toString().trim() === '')) {
error = rule.required;
} else if (rule.pattern && !rule.pattern.test(value)) {
error = rule.patternMessage || '格式不正确';
} else if (rule.minLength && value.length < rule.minLength) {
error = `最少需要 ${rule.minLength} 个字符`;
} else if (rule.maxLength && value.length > rule.maxLength) {
error = `最多允许 ${rule.maxLength} 个字符`;
} else if (rule.custom) {
error = rule.custom(value, values);
}
setErrors(prev => ({ ...prev, [name]: error }));
return error;
}, [validationRules, values]);
const validateAll = useCallback(() => {
const newErrors = {};
let isValid = true;
Object.keys(validationRules).forEach(name => {
const error = validateField(name, values[name]);
if (error) {
newErrors[name] = error;
isValid = false;
}
});
setErrors(newErrors);
setTouched(Object.keys(validationRules).reduce((acc, key) => {
acc[key] = true;
return acc;
}, {}));
return isValid;
}, [validationRules, values, validateField]);
const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
}, [initialValues]);
return {
values,
errors,
touched,
setValue,
setTouched,
validateAll,
reset,
isValid: Object.keys(errors).length === 0
};
}
// 使用表单验证 Hook 的组件
function RegistrationForm() {
const validationRules = {
username: {
required: '用户名不能为空',
minLength: 3,
maxLength: 20,
pattern: /^[a-zA-Z0-9_]+$/,
patternMessage: '用户名只能包含字母、数字和下划线'
},
email: {
required: '邮箱不能为空',
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
patternMessage: '请输入有效的邮箱地址'
},
password: {
required: '密码不能为空',
minLength: 8,
custom: (value) => {
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
return '密码必须包含大小写字母和数字';
}
return '';
}
},
confirmPassword: {
required: '请确认密码',
custom: (value, allValues) => {
if (value !== allValues.password) {
return '两次输入的密码不一致';
}
return '';
}
}
};
const {
values,
errors,
touched,
setValue,
setTouched,
validateAll,
reset,
isValid
} = useFormValidation({
username: '',
email: '',
password: '',
confirmPassword: ''
}, validationRules);
const handleSubmit = (e) => {
e.preventDefault();
if (validateAll()) {
console.log('表单提交:', values);
alert('注册成功!');
reset();
}
};
const handleInputChange = (name) => (e) => {
setValue(name, e.target.value);
};
const handleInputBlur = (name) => () => {
setTouched(name);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>用户名:</label>
<input
type="text"
value={values.username}
onChange={handleInputChange('username')}
onBlur={handleInputBlur('username')}
/>
{touched.username && errors.username && (
<span style={{ color: 'red' }}>{errors.username}</span>
)}
</div>
<div>
<label>邮箱:</label>
<input
type="email"
value={values.email}
onChange={handleInputChange('email')}
onBlur={handleInputBlur('email')}
/>
{touched.email && errors.email && (
<span style={{ color: 'red' }}>{errors.email}</span>
)}
</div>
<div>
<label>密码:</label>
<input
type="password"
value={values.password}
onChange={handleInputChange('password')}
onBlur={handleInputBlur('password')}
/>
{touched.password && errors.password && (
<span style={{ color: 'red' }}>{errors.password}</span>
)}
</div>
<div>
<label>确认密码:</label>
<input
type="password"
value={values.confirmPassword}
onChange={handleInputChange('confirmPassword')}
onBlur={handleInputBlur('confirmPassword')}
/>
{touched.confirmPassword && errors.confirmPassword && (
<span style={{ color: 'red' }}>{errors.confirmPassword}</span>
)}
</div>
<div>
<button type="submit" disabled={!isValid}>
注册
</button>
<button type="button" onClick={reset}>
重置
</button>
</div>
</form>
);
}
Hooks 最佳实践
1. Hooks 规则
// ✅ 正确:在函数组件顶层调用 Hooks
function MyComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return <div>{count}</div>;
}
// ❌ 错误:在条件语句中调用 Hooks
function BadComponent({ shouldUseCount }) {
if (shouldUseCount) {
const [count, setCount] = useState(0); // 违反 Hooks 规则
}
return <div>Bad</div>;
}
// ✅ 正确:条件逻辑在 Hook 内部
function GoodComponent({ shouldUseCount }) {
const [count, setCount] = useState(0);
useEffect(() => {
if (shouldUseCount) {
// 条件逻辑在 Hook 内部
document.title = `Count: ${count}`;
}
}, [count, shouldUseCount]);
return <div>{shouldUseCount ? count : 'No count'}</div>;
}
2. 依赖数组最佳实践
// ✅ 正确:包含所有依赖
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // 包含 userId 依赖
return <div>{user?.name}</div>;
}
// ❌ 错误:遗漏依赖
function BadUserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // 遗漏 userId 依赖
return <div>{user?.name}</div>;
}
// ✅ 正确:使用 useCallback 稳定函数引用
function SearchComponent({ onSearch }) {
const [query, setQuery] = useState('');
const debouncedSearch = useCallback(
debounce((searchQuery) => {
onSearch(searchQuery);
}, 300),
[onSearch]
);
useEffect(() => {
if (query) {
debouncedSearch(query);
}
}, [query, debouncedSearch]);
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
);
}
3. 性能优化技巧
// 使用 React.memo 优化子组件
const ExpensiveChild = memo(({ data, onUpdate }) => {
console.log('ExpensiveChild 渲染');
return (
<div>
{data.map(item => (
<div key={item.id}>
{item.name}
<button onClick={() => onUpdate(item.id)}>
更新
</button>
</div>
))}
</div>
);
});
function OptimizedParent() {
const [items, setItems] = useState([]);
const [otherState, setOtherState] = useState(0);
// 使用 useCallback 稳定函数引用
const handleUpdate = useCallback((id) => {
setItems(prevItems =>
prevItems.map(item =>
item.id === id
? { ...item, updated: Date.now() }
: item
)
);
}, []);
// 使用 useMemo 缓存计算结果
const expensiveData = useMemo(() => {
return items.filter(item => item.active)
.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
return (
<div>
<button onClick={() => setOtherState(otherState + 1)}>
其他状态: {otherState}
</button>
<ExpensiveChild
data={expensiveData}
onUpdate={handleUpdate}
/>
</div>
);
}
4. 错误处理
// 错误边界 Hook
function useErrorHandler() {
const [error, setError] = useState(null);
const resetError = useCallback(() => {
setError(null);
}, []);
const captureError = useCallback((error) => {
console.error('捕获到错误:', error);
setError(error);
}, []);
return { error, resetError, captureError };
}
// 异步操作错误处理
function useAsyncOperation() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const execute = useCallback(async (asyncFunction) => {
try {
setLoading(true);
setError(null);
const result = await asyncFunction();
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
}, []);
return { loading, error, execute };
}
// 使用错误处理的组件
function DataComponent() {
const [data, setData] = useState(null);
const { loading, error, execute } = useAsyncOperation();
const fetchData = useCallback(async () => {
const result = await execute(async () => {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('数据获取失败');
}
return response.json();
});
setData(result);
}, [execute]);
useEffect(() => {
fetchData();
}, [fetchData]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return (
<div>
<h3>数据展示</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={fetchData}>重新获取</button>
</div>
);
}
总结
React Hooks 是现代 React 开发的核心技术,它们提供了一种更简洁、更灵活的方式来管理组件状态和副作用。通过掌握基础 Hooks(useState、useEffect、useContext)和高级 Hooks(useReducer、useMemo、useCallback),以及学会创建自定义 Hooks,你可以构建更加高效和可维护的 React 应用。
关键要点
- 遵循 Hooks 规则:只在函数组件顶层调用 Hooks
- 正确使用依赖数组:确保包含所有依赖项
- 性能优化:合理使用 useMemo 和 useCallback
- 自定义 Hooks:提取可重用的逻辑
- 错误处理:构建健壮的应用
掌握这些概念和最佳实践,你就能充分发挥 React Hooks 的威力,构建出色的用户界面。