1. Hooks概述
什么是Hooks?
Hooks是React 16.8引入的新特性,让你能在函数组件中使用状态和其他React特性,而不需要编写类组件。
Hooks的优势
- 更简洁的代码:减少样板代码
- 更好的逻辑复用:通过自定义Hook实现
- 更容易测试:纯函数更易测试
- 更好的性能:避免类组件的开销
2. 基础Hooks
2.1 useState - 状态管理
基本用法
jsx
import { useState } from 'react';
function Counter() {
// 声明状态变量
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="输入姓名"
/>
</div>
);
}
复杂状态管理
jsx
function UserForm() {
// 对象状态
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
// 数组状态
const [hobbies, setHobbies] = useState([]);
// 更新对象状态
const updateUser = (field, value) => {
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
// 添加爱好
const addHobby = (hobby) => {
setHobbies(prevHobbies => [...prevHobbies, hobby]);
};
// 删除爱好
const removeHobby = (index) => {
setHobbies(prevHobbies => prevHobbies.filter((_, i) => i !== index));
};
return (
<form>
<input
value={user.name}
onChange={e => updateUser('name', e.target.value)}
placeholder="姓名"
/>
<input
value={user.email}
onChange={e => updateUser('email', e.target.value)}
placeholder="邮箱"
/>
<div>
<h3>爱好列表:</h3>
{hobbies.map((hobby, index) => (
<div key={index}>
<span>{hobby}</span>
<button onClick={() => removeHobby(index)}>删除</button>
</div>
))}
</div>
</form>
);
}
useState简化实现
javascript
// 简化的useState实现
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
renderWithHooks(); // 重新渲染
};
}
function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
function renderWithHooks() {
cursor = 0;
// 重新渲染组件逻辑
}
2.2 useEffect - 副作用处理
基本用法
jsx
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 组件挂载和userId变化时执行
useEffect(() => {
console.log('Effect运行');
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (error) {
console.error('获取用户失败:', error);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // 依赖数组
// 只在组件挂载时执行一次
useEffect(() => {
document.title = `用户资料 - ${user?.name || '加载中'}`;
}, [user?.name]);
// 清理副作用
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
// 返回清理函数
return () => {
clearInterval(timer);
console.log('定时器清理');
};
}, []);
if (loading) return <div>加载中...</div>;
return (
<div>
<h1>{user?.name}</h1>
<p>{user?.email}</p>
</div>
);
}
高级useEffect模式
jsx
function AdvancedEffects() {
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const [posts, setPosts] = useState([]);
// 防抖Effect
useEffect(() => {
const timeoutId = setTimeout(() => {
console.log('防抖执行');
}, 500);
return () => clearTimeout(timeoutId);
}, [windowWidth]);
// 窗口大小监听
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// 异步数据获取与取消
useEffect(() => {
let cancelled = false;
const fetchPosts = async () => {
try {
const response = await fetch('/api/posts');
const data = await response.json();
if (!cancelled) {
setPosts(data);
}
} catch (error) {
if (!cancelled) {
console.error('获取文章失败:', error);
}
}
};
fetchPosts();
return () => {
cancelled = true;
};
}, []);
return (
<div>
<p>窗口宽度: {windowWidth}</p>
<p>文章数量: {posts.length}</p>
</div>
);
}
useEffect简化实现
javascript
// 简化的useEffect实现
let effects = [];
let effectCursor = 0;
function useEffect(callback, depArray) {
const hasNoDeps = !depArray;
const deps = effects[effectCursor] ? effects[effectCursor].deps : undefined;
const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true;
if (hasNoDeps || hasChangedDeps) {
// 执行清理函数
if (effects[effectCursor] && effects[effectCursor].cleanup) {
effects[effectCursor].cleanup();
}
// 执行副作用
const cleanup = callback();
effects[effectCursor] = {
deps: depArray,
cleanup
};
}
effectCursor++;
}
3. 高级Hooks
3.1 useReducer - 复杂状态管理
基本用法
jsx
import { useReducer } from 'react';
// 定义reducer函数
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
case 'set':
return { count: action.payload };
default:
throw new Error(`未知的action类型: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
<button onClick={() => dispatch({ type: 'set', payload: 10 })}>设为10</button>
</div>
);
}
复杂状态管理示例
jsx
// 购物车reducer
function 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
)
};
} else {
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: action.payload.quantity }
: item
)
};
case 'CLEAR_CART':
return {
...state,
items: []
};
default:
return state;
}
}
function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, { items: [] });
const addItem = (product) => {
dispatch({ type: 'ADD_ITEM', payload: product });
};
const removeItem = (productId) => {
dispatch({ type: 'REMOVE_ITEM', payload: productId });
};
const updateQuantity = (productId, quantity) => {
dispatch({
type: 'UPDATE_QUANTITY',
payload: { id: productId, quantity }
});
};
const totalPrice = cart.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
return (
<div>
<h2>购物车</h2>
{cart.items.map(item => (
<div key={item.id}>
<span>{item.name} - ¥{item.price}</span>
<input
type="number"
value={item.quantity}
onChange={e => updateQuantity(item.id, parseInt(e.target.value))}
min="1"
/>
<button onClick={() => removeItem(item.id)}>删除</button>
</div>
))}
<p>总价: ¥{totalPrice}</p>
<button onClick={() => dispatch({ type: 'CLEAR_CART' })}>
清空购物车
</button>
</div>
);
}
3.2 useContext - 跨组件状态共享
基本用法
jsx
import { createContext, useContext, useReducer } from 'react';
// 创建Context
const AuthContext = createContext();
// Auth reducer
function authReducer(state, action) {
switch (action.type) {
case 'LOGIN':
return {
...state,
isAuthenticated: true,
user: action.payload.user,
token: action.payload.token
};
case 'LOGOUT':
return {
...state,
isAuthenticated: false,
user: null,
token: null
};
case 'UPDATE_USER':
return {
...state,
user: { ...state.user, ...action.payload }
};
default:
return state;
}
}
// AuthProvider组件
function AuthProvider({ children }) {
const [state, dispatch] = useReducer(authReducer, {
isAuthenticated: false,
user: null,
token: null,
loading: false
});
const login = async (email, password) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
dispatch({
type: 'LOGIN',
payload: { user: data.user, token: data.token }
});
localStorage.setItem('token', data.token);
} catch (error) {
console.error('登录失败:', error);
}
};
const logout = () => {
dispatch({ type: 'LOGOUT' });
localStorage.removeItem('token');
};
const updateUser = (userData) => {
dispatch({ type: 'UPDATE_USER', payload: userData });
};
return (
<AuthContext.Provider value={{
...state,
login,
logout,
updateUser
}}>
{children}
</AuthContext.Provider>
);
}
// 自定义Hook
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth必须在AuthProvider内部使用');
}
return context;
}
// 使用Context的组件
function LoginForm() {
const { login, isAuthenticated } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return <div>已登录</div>;
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="邮箱"
/>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="密码"
/>
<button type="submit">登录</button>
</form>
);
}
function UserProfile() {
const { user, updateUser, logout } = useAuth();
return (
<div>
<h2>用户资料</h2>
<p>姓名: {user?.name}</p>
<p>邮箱: {user?.email}</p>
<button onClick={() => updateUser({ name: '新姓名' })}>
更新姓名
</button>
<button onClick={logout}>退出登录</button>
</div>
);
}
3.3 useMemo - 性能优化
基本用法
jsx
import { useState, useMemo } from 'react';
function ExpensiveComponent({ items, filter }) {
const [count, setCount] = useState(0);
// 缓存昂贵的计算
const filteredItems = useMemo(() => {
console.log('执行过滤计算');
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);
// 缓存派生数据
const stats = useMemo(() => {
console.log('计算统计数据');
return {
total: filteredItems.length,
completed: filteredItems.filter(item => item.completed).length,
pending: filteredItems.filter(item => !item.completed).length
};
}, [filteredItems]);
// 复杂计算示例
const expensiveValue = useMemo(() => {
console.log('执行复杂计算');
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.random();
}
return result;
}, [items.length]); // 只有当items数量变化时才重新计算
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(c => c + 1)}>增加计数</button>
<div>
<h3>统计信息</h3>
<p>总计: {stats.total}</p>
<p>已完成: {stats.completed}</p>
<p>待处理: {stats.pending}</p>
<p>复杂计算结果: {expensiveValue.toFixed(2)}</p>
</div>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
3.4 useCallback - 函数缓存
基本用法
jsx
import { useState, useCallback, memo } from 'react';
// 子组件使用memo包装
const ChildComponent = memo(function ChildComponent({ onClick, data }) {
console.log('ChildComponent渲染');
return (
<div>
<p>{data.name}</p>
<button onClick={onClick}>点击</button>
</div>
);
});
function ParentComponent({ items }) {
const [count, setCount] = useState(0);
const [selectedId, setSelectedId] = useState(null);
// 缓存回调函数
const handleItemClick = useCallback((itemId) => {
console.log('点击了项目:', itemId);
setSelectedId(itemId);
}, []);
// 带参数的回调缓存
const handleItemUpdate = useCallback((itemId, newData) => {
console.log('更新项目:', itemId, newData);
// 更新逻辑
}, []);
// 复杂的回调逻辑
const processItem = useCallback((item) => {
// 只有当selectedId变化时才重新创建函数
return {
...item,
isSelected: item.id === selectedId,
onClick: () => handleItemClick(item.id)
};
}, [selectedId, handleItemClick]);
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(c => c + 1)}>
增加计数
</button>
{items.map(item => {
const processedItem = processItem(item);
return (
<ChildComponent
key={item.id}
data={processedItem}
onClick={processedItem.onClick}
/>
);
})}
</div>
);
}
useCallback与事件处理
jsx
function TodoList({ todos, onToggle, onDelete, onEdit }) {
const [editingId, setEditingId] = useState(null);
const [editText, setEditText] = useState('');
// 缓存编辑相关函数
const startEdit = useCallback((todo) => {
setEditingId(todo.id);
setEditText(todo.text);
}, []);
const saveEdit = useCallback(() => {
if (editingId && editText.trim()) {
onEdit(editingId, editText);
setEditingId(null);
setEditText('');
}
}, [editingId, editText, onEdit]);
const cancelEdit = useCallback(() => {
setEditingId(null);
setEditText('');
}, []);
// 缓存Toggle函数
const handleToggle = useCallback((todoId) => {
onToggle(todoId);
}, [onToggle]);
// 缓存删除函数
const handleDelete = useCallback((todoId) => {
if (window.confirm('确定删除吗?')) {
onDelete(todoId);
}
}, [onDelete]);
return (
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
isEditing={editingId === todo.id}
editText={editText}
onToggle={() => handleToggle(todo.id)}
onDelete={() => handleDelete(todo.id)}
onStartEdit={() => startEdit(todo)}
onSaveEdit={saveEdit}
onCancelEdit={cancelEdit}
onEditTextChange={setEditText}
/>
))}
</div>
);
}
const TodoItem = memo(function TodoItem({
todo,
isEditing,
editText,
onToggle,
onDelete,
onStartEdit,
onSaveEdit,
onCancelEdit,
onEditTextChange
}) {
if (isEditing) {
return (
<div>
<input
value={editText}
onChange={e => onEditTextChange(e.target.value)}
onKeyPress={e => e.key === 'Enter' && onSaveEdit()}
/>
<button onClick={onSaveEdit}>保存</button>
<button onClick={onCancelEdit}>取消</button>
</div>
);
}
return (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={onToggle}
/>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
>
{todo.text}
</span>
<button onClick={onStartEdit}>编辑</button>
<button onClick={onDelete}>删除</button>
</div>
);
});
4. 其他有用的Hooks
4.1 useRef - 引用DOM和保存变量
jsx
import { useRef, useEffect, useState } from 'react';
function RefExamples() {
const inputRef = useRef(null);
const timerRef = useRef(null);
const countRef = useRef(0);
const [renderCount, setRenderCount] = useState(0);
// 聚焦输入框
const focusInput = () => {
inputRef.current?.focus();
};
// 启动定时器
const startTimer = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
timerRef.current = setInterval(() => {
countRef.current += 1;
console.log('定时器计数:', countRef.current);
}, 1000);
};
// 停止定时器
const stopTimer = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
// 获取前一次的值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const prevRenderCount = usePrevious(renderCount);
useEffect(() => {
return () => {
// 组件卸载时清理定时器
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
return (
<div>
<input ref={inputRef} placeholder="点击按钮聚焦" />
<button onClick={focusInput}>聚焦输入框</button>
<div>
<p>渲染次数: {renderCount}</p>
<p>上次渲染次数: {prevRenderCount}</p>
<button onClick={() => setRenderCount(c => c + 1)}>
触发重新渲染
</button>
</div>
<div>
<button onClick={startTimer}>启动定时器</button>
<button onClick={stopTimer}>停止定时器</button>
<p>定时器计数: {countRef.current}</p>
</div>
</div>
);
}
4.2 useLayoutEffect - 同步副作用
jsx
import { useLayoutEffect, useEffect, useRef, useState } from 'react';
function LayoutEffectExample() {
const [width, setWidth] = useState(0);
const divRef = useRef(null);
// useLayoutEffect在DOM更新后同步执行
useLayoutEffect(() => {
if (divRef.current) {
const rect = divRef.current.getBoundingClientRect();
setWidth(rect.width);
}
});
// useEffect异步执行,可能造成闪烁
useEffect(() => {
console.log('useEffect执行');
});
return (
<div>
<div ref={divRef} style={{ width: '50%', background: 'lightblue' }}>
这个div的宽度是: {width}px
</div>
</div>
);
}
5. 自定义Hooks
5.1 数据获取Hook
jsx
import { useState, useEffect, useRef } from 'react';
function useApi(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const cancelRef = useRef();
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
try {
setLoading(true);
setError(null);
// 取消之前的请求
if (cancelRef.current) {
cancelRef.current.abort();
}
// 创建新的AbortController
const controller = new AbortController();
cancelRef.current = controller;
const response = await fetch(url, {
...options,
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (!cancelled) {
setData(result);
}
} catch (err) {
if (!cancelled && err.name !== 'AbortError') {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
fetchData();
return () => {
cancelled = true;
if (cancelRef.current) {
cancelRef.current.abort();
}
};
}, [url, JSON.stringify(options)]);
const refetch = () => {
setLoading(true);
setError(null);
// 触发重新获取
};
return { data, loading, error, refetch };
}
// 使用示例
function UserList() {
const { data: users, loading, error, refetch } = useApi('/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return (
<div>
<button onClick={refetch}>刷新</button>
{users?.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}
5.2 本地存储Hook
jsx
import { useState, useEffect } from 'react';
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 = (value) => {
try {
// 允许传入函数
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);
}
};
// 监听localStorage变化
useEffect(() => {
const handleStorageChange = (e) => {
if (e.key === key && e.newValue !== null) {
try {
setStoredValue(JSON.parse(e.newValue));
} catch (error) {
console.error('Error parsing localStorage value:', error);
}
}
};
window.addEventListener('storage', handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, [key]);
return [storedValue, setValue];
}
// 使用示例
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'zh-CN');
return (
<div>
<h3>设置</h3>
<label>
主题:
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">亮色</option>
<option value="dark">暗色</option>
</select>
</label>
<label>
语言:
<select value={language} onChange={e => setLanguage(e.target.value)}>
<option value="zh-CN">中文</option>
<option value="en-US">English</option>
</select>
</label>
</div>
);
}
5.3 防抖和节流Hook
jsx
import { useState, useEffect, useRef, useCallback } from 'react';
// 防抖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 useDebounceCallback(callback, delay, deps) {
const timeoutRef = useRef();
const debouncedCallback = useCallback((...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
}, [callback, delay, ...deps]);
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return debouncedCallback;
}
// 节流Hook
function useThrottle(value, delay) {
const [throttledValue, setThrottledValue] = useState(value);
const lastExecuted = useRef(Date.now());
useEffect(() => {
if (Date.now() >= lastExecuted.current + delay) {
lastExecuted.current = Date.now();
setThrottledValue(value);
} else {
const timerId = setTimeout(() => {
lastExecuted.current = Date.now();
setThrottledValue(value);
}, delay);
return () => clearTimeout(timerId);
}
}, [value, delay]);
return throttledValue;
}
// 使用示例
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
// 防抖搜索词
const debouncedSearchTerm = useDebounce(searchTerm, 500);
// 防抖搜索函数
const debouncedSearch = useDebounceCallback(async (term) => {
if (term) {
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(term)}`);
const data = await response.json();
setResults(data);
} catch (error) {
console.error('搜索失败:', error);
}
} else {
setResults([]);
}
}, 300, []);
// 监听防抖后的搜索词
useEffect(() => {
debouncedSearch(debouncedSearchTerm);
}, [debouncedSearchTerm, debouncedSearch]);
return (
<div>
<input
type="text"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
placeholder="输入搜索关键词"
/>
<div>
{results.map(result => (
<div key={result.id}>{result.title}</div>
))}
</div>
</div>
);
}
6. Hooks规则和注意事项
6.1 Hooks规则
jsx
// ✅ 正确:在函数组件顶层调用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() {
const [items, setItems] = useState([]);
for (let i = 0; i < items.length; i++) {
const [itemState, setItemState] = useState(null); // 错误!
}
return <div></div>;
}
// ❌ 错误:在条件语句中调用Hooks
function AnotherBadComponent({ shouldShowName }) {
const [count, setCount] = useState(0);
if (shouldShowName) {
const [name, setName] = useState(''); // 错误!
}
return <div>{count}</div>;
}
// ✅ 正确:条件逻辑在Hook内部
function GoodComponent({ shouldShowName }) {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return (
<div>
{count}
{shouldShowName && <span>{name}</span>}
</div>
);
}
6.2 常见陷阱和解决方案
jsx
// 陷阱1: 在useEffect中缺少依赖
function BadEffect({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // 缺少userId依赖
return <div>{user?.name}</div>;
}
// 解决方案
function GoodEffect({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // 包含userId依赖
return <div>{user?.name}</div>;
}
// 陷阱2: 状态更新依赖当前状态
function BadCounter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1); // 可能出现竞态条件
};
return <button onClick={increment}>{count}</button>;
}
// 解决方案
function GoodCounter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => prevCount + 1); // 使用函数式更新
};
return <button onClick={increment}>{count}</button>;
}
// 陷阱3: useEffect清理不当
function BadTimer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// 忘记清理定时器
}, []);
return <div>{seconds}</div>;
}
// 解决方案
function GoodTimer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(interval); // 正确清理
}, []);
return <div>{seconds}</div>;
}
7. Hooks原理简化实现
javascript
// React Hooks的简化实现
let hookIndex = 0;
let hooks = [];
// 模拟React的调度系统
function scheduleRender() {
hookIndex = 0;
renderComponent();
}
// useState实现
function useState(initialValue) {
const currentIndex = hookIndex;
if (hooks[currentIndex] === undefined) {
hooks[currentIndex] = initialValue;
}
const setState = (newValue) => {
if (typeof newValue === 'function') {
hooks[currentIndex] = newValue(hooks[currentIndex]);
} else {
hooks[currentIndex] = newValue;
}
scheduleRender();
};
hookIndex++;
return [hooks[currentIndex], setState];
}
// useEffect实现
function useEffect(callback, dependencies) {
const currentIndex = hookIndex;
const hasChanged = hasDepArrayChanged(
hooks[currentIndex] ? hooks[currentIndex].dependencies : undefined,
dependencies
);
if (!hooks[currentIndex] || hasChanged) {
// 执行清理函数
if (hooks[currentIndex] && hooks[currentIndex].cleanup) {
hooks[currentIndex].cleanup();
}
// 执行effect
const cleanup = callback();
hooks[currentIndex] = {
dependencies,
cleanup
};
}
hookIndex++;
}
// 检查依赖数组是否变化
function hasDepArrayChanged(prevDeps, deps) {
if (prevDeps === null) return true;
if (deps === null) return true;
if (prevDeps.length !== deps.length) return true;
for (let i = 0; i < prevDeps.length; i++) {
if (prevDeps[i] !== deps[i]) {
return true;
}
}
return false;
}
// useReducer实现
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
const dispatch = (action) => {
const newState = reducer(state, action);
setState(newState);
};
return [state, dispatch];
}
// useMemo实现
function useMemo(factory, dependencies) {
const currentIndex = hookIndex;
const hasChanged = hasDepArrayChanged(
hooks[currentIndex] ? hooks[currentIndex].dependencies : undefined,
dependencies
);
if (!hooks[currentIndex] || hasChanged) {
const value = factory();
hooks[currentIndex] = {
value,
dependencies
};
}
hookIndex++;
return hooks[currentIndex].value;
}
// useCallback实现
function useCallback(callback, dependencies) {
return useMemo(() => callback, dependencies);
}
8. 测试Hooks
jsx
import { renderHook, act } from '@testing-library/react-hooks';
import { useState } from 'react';
// 测试自定义Hook
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(c => c + 1);
const decrement = () => setCount(c => c - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
// 测试用例
describe('useCounter', () => {
test('初始值应该正确', () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
test('increment应该增加计数', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
test('reset应该重置到初始值', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.increment();
result.current.increment();
});
expect(result.current.count).toBe(7);
act(() => {
result.current.reset();
});
expect(result.current.count).toBe(5);
});
});
React Hooks是现代React开发的核心特性,它们提供了一种更简洁、更强大的方式来管理组件状态和副作用。掌握这些Hooks的用法和原理,将大大提升你的React开发能力。
记住Hooks的两个基本规则:
- 只能在函数组件的顶层调用Hooks
- 只能在React函数组件和自定义Hook中调用Hooks
通过合理使用内置Hooks和创建自定义Hooks,你可以构建出更加优雅和可维护的React应用。