React Hooks完全指南
1. Hooks概述
什么是Hooks?
Hooks是React 16.8引入的新特性,让你能在函数组件中使用状态和其他React特性,而不需要编写类组件。
Hooks的优势
- 更简洁的代码:减少样板代码
- 更好的逻辑复用:通过自定义Hook实现
- 更容易测试:纯函数更易测试
- 更好的性能:避免类组件的开销
2. 基础Hooks
2.1 useState - 状态管理
基本用法
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>);
}
复杂状态管理
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简化实现
// 简化的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 - 副作用处理
基本用法
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模式
function AdvancedEffects() {const [windowWidth, setWindowWidth] = useState(window.innerWidth);const [posts, setPosts] = useState([]);// 防抖EffectuseEffect(() => {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简化实现
// 简化的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 - 复杂状态管理
基本用法
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>);
}
复杂状态管理示例
// 购物车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><inputtype="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 - 跨组件状态共享
基本用法
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}><inputtype="email"value={email}onChange={e => setEmail(e.target.value)}placeholder="邮箱"/><inputtype="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 - 性能优化
基本用法
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 - 函数缓存
基本用法
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 (<ChildComponentkey={item.id}data={processedItem}onClick={processedItem.onClick}/>);})}</div>);
}
useCallback与事件处理
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 => (<TodoItemkey={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><inputvalue={editText}onChange={e => onEditTextChange(e.target.value)}onKeyPress={e => e.key === 'Enter' && onSaveEdit()}/><button onClick={onSaveEdit}>保存</button><button onClick={onCancelEdit}>取消</button></div>);}return (<div><inputtype="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和保存变量
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 - 同步副作用
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
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();}// 创建新的AbortControllerconst 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
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
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><inputtype="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规则
// ✅ 正确:在函数组件顶层调用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 常见陷阱和解决方案
// 陷阱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原理简化实现
// 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();}// 执行effectconst 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
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应用。