引言
React Hooks的引入彻底改变了React组件的编写方式,让函数式组件能够拥有类组件的特性,如状态管理、副作用处理等。随着React生态的发展,Hooks的应用已经从简单的状态管理扩展到了复杂的业务逻辑处理。本文将深入探讨React Hooks的核心概念和高级用法,帮助开发者掌握如何在实际项目中高效地使用Hooks,并通过自定义Hook来封装复用性强的业务逻辑。
React Hooks核心概念回顾
什么是React Hooks
React Hooks是React 16.8版本引入的一组函数,允许我们在函数组件中"钩入"React的状态和生命周期特性。Hooks解决了传统类组件中的许多问题,包括:
- 状态逻辑难以复用
- 组件间状态共享困难
- 类组件的复杂性
- this指向问题
基础Hook概览
React提供了几个核心的内置Hook:
- useState - 状态管理
- useEffect - 副作用处理
- useContext - 上下文访问
- useReducer - 复杂状态管理
- useCallback - 函数记忆化
- useMemo - 计算结果缓存
- useRef - 引用访问
useEffect深度解析与高级应用
useEffect基础用法
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 数据获取逻辑
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
setLoading(false);
} catch (error) {
console.error('Failed to fetch user:', error);
setLoading(false);
}
};
if (userId) {
fetchUser();
}
}, [userId]); // 依赖数组
if (loading) return <div>Loading...</div>;
return <div>{user?.name}</div>;
}
useEffect的依赖数组陷阱
// ❌ 错误示例:缺少依赖项
useEffect(() => {
console.log(user.name);
}, []); // 没有包含user,可能导致闭包问题
// ✅ 正确示例:正确处理依赖
useEffect(() => {
console.log(user.name);
}, [user]); // 包含所有使用的变量
// ⚠️ 特殊情况:需要避免无限循环
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1); // 使用函数式更新避免闭包问题
}, 1000);
return () => clearInterval(timer); // 清理副作用
}, []);
高级useEffect模式
数据获取与错误处理
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// 使用示例
function UserList() {
const { data: users, loading, error } = useApi('/api/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
防抖处理
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// 使用示例
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearch) {
// 执行搜索逻辑
console.log('Searching for:', debouncedSearch);
}
}, [debouncedSearch]);
return (
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}
useContext与状态管理
Context的正确使用方式
import React, { createContext, useContext, useReducer } from 'react';
// 创建Context
const AppContext = createContext();
// 定义reducer
const appReducer = (state, action) => {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'SET_THEME':
return { ...state, theme: action.payload };
case 'TOGGLE_LOADING':
return { ...state, loading: action.payload };
default:
return state;
}
};
// Provider组件
export function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, {
user: null,
theme: 'light',
loading: false
});
const value = {
...state,
setUser: (user) => dispatch({ type: 'SET_USER', payload: user }),
setTheme: (theme) => dispatch({ type: 'SET_THEME', payload: theme }),
setLoading: (loading) => dispatch({ type: 'TOGGLE_LOADING', payload: loading })
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
// 自定义Hook
export function useApp() {
const context = useContext(AppContext);
if (!context) {
throw new Error('useApp must be used within AppProvider');
}
return context;
}
Context性能优化
// ❌ 不推荐:每次渲染都创建新对象
function BadExample() {
const [count, setCount] = useState(0);
return (
<AppContext.Provider value={{ count, setCount }}>
{/* 组件树 */}
</AppContext.Provider>
);
}
// ✅ 推荐:使用useMemo优化
function GoodExample() {
const [count, setCount] = useState(0);
const contextValue = useMemo(() => ({
count,
setCount
}), [count]);
return (
<AppContext.Provider value={contextValue}>
{/* 组件树 */}
</AppContext.Provider>
);
}
useReducer与复杂状态管理
状态机模式实现
// 定义状态类型
const STATUS = {
IDLE: 'IDLE',
LOADING: 'LOADING',
SUCCESS: 'SUCCESS',
ERROR: 'ERROR'
};
// 定义action类型
const ACTION_TYPES = {
FETCH_START: 'FETCH_START',
FETCH_SUCCESS: 'FETCH_SUCCESS',
FETCH_ERROR: 'FETCH_ERROR'
};
// 状态管理Hook
function useAsyncState(initialState = STATUS.IDLE) {
const [state, dispatch] = useReducer((prevState, action) => {
switch (action.type) {
case ACTION_TYPES.FETCH_START:
return STATUS.LOADING;
case ACTION_TYPES.FETCH_SUCCESS:
return STATUS.SUCCESS;
case ACTION_TYPES.FETCH_ERROR:
return STATUS.ERROR;
default:
return prevState;
}
}, initialState);
const start = () => dispatch({ type: ACTION_TYPES.FETCH_START });
const success = () => dispatch({ type: ACTION_TYPES.FETCH_SUCCESS });
const error = () => dispatch({ type: ACTION_TYPES.FETCH_ERROR });
return [state, { start, success, error }];
}
// 使用示例
function UserProfile({ userId }) {
const [status, { start, success, error }] = useAsyncState();
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
start();
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
success();
} catch (err) {
error();
console.error('Failed to fetch user:', err);
}
};
if (userId) {
fetchUser();
}
}, [userId]);
return (
<div>
{status === STATUS.LOADING && <div>Loading...</div>}
{status === STATUS.ERROR && <div>Error occurred</div>}
{status === STATUS.SUCCESS && user && <div>{user.name}</div>}
</div>
);
}
复杂表单状态管理
const FORM_ACTIONS = {
SET_FIELD: 'SET_FIELD',
SET_ERRORS: 'SET_ERRORS',
RESET_FORM: 'RESET_FORM',
VALIDATE_FIELD: 'VALIDATE_FIELD'
};
function useForm(initialState, validationRules) {
const [formState, dispatch] = useReducer((state, action) => {
switch (action.type) {
case FORM_ACTIONS.SET_FIELD:
return {
...state,
[action.field]: action.value,
errors: {
...state.errors,
[action.field]: validateField(
action.field,
action.value,
validationRules[action.field]
)
}
};
case FORM_ACTIONS.SET_ERRORS:
return { ...state, errors: action.errors };
case FORM_ACTIONS.RESET_FORM:
return initialState;
case FORM_ACTIONS.VALIDATE_FIELD:
return {
...state,
errors: {
...state.errors,
[action.field]: validateField(
action.field,
state[action.field],
validationRules[action.field]
)
}
};
default:
return state;
}
}, initialState);
const setField = (field, value) => {
dispatch({ type: FORM_ACTIONS.SET_FIELD, field, value });
};
const validateForm = () => {
const errors = {};
Object.keys(validationRules).forEach(field => {
errors[field] = validateField(
field,
formState[field],
validationRules[field]
);
});
dispatch({ type: FORM_ACTIONS.SET_ERRORS, errors });
return !Object.values(errors).some(error => error);
};
const resetForm = () => {
dispatch({ type: FORM_ACTIONS.RESET_FORM });
};
return [
formState,
{ setField, validateForm, resetForm }
];
}
// 验证函数
function validateField(field, value, rules) {
if (!rules) return '';
for (const rule of rules) {
if (!rule.test(value)) {
return rule.message;
}
}
return '';
}
// 使用示例
function RegistrationForm() {
const validationRules = {
email: [
{ test: v => v.includes('@'), message: 'Email is required' },
{ test: v => v.includes('.'), message: 'Valid email required' }
],
password: [
{ test: v => v.length >= 8, message: 'Password must be at least 8 characters' }
]
};
const [formState, { setField, validateForm }] = useForm({
email: '',
password: '',
errors: {}
}, validationRules);
const handleSubmit = (e) => {
e.preventDefault();
if (validateForm()) {
console.log('Form submitted:', formState);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={formState.email}
onChange={(e) => setField('email', e.target.value)}
placeholder="Email"
/>
{formState.errors.email && <span>{formState.errors.email}</span>}
<input
type="password"
value={formState.password}
onChange={(e) => setField('password', e.target.value)}
placeholder="Password"
/>
{formState.errors.password && <span>{formState.errors.password}</span>}
<button type="submit">Register</button>
</form>
);
}
useCallback与useMemo性能优化
函数记忆化最佳实践
// ❌ 不推荐:每次渲染都创建新函数
function BadComponent({ items, onItemSelect }) {
const handleItemClick = (item) => {
// 每次渲染都会创建新的函数
onItemSelect(item);
};
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => handleItemClick(item)}>
{item.name}
</li>
))}
</ul>
);
}
// ✅ 推荐:使用useCallback
function GoodComponent({ items, onItemSelect }) {
const handleItemClick = useCallback((item) => {
onItemSelect(item);
}, [onItemSelect]);
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => handleItemClick(item)}>
{item.name}
</li>
))}
</ul>
);
}
// ✅ 更好的方式:将函数作为props传递
function BetterComponent({ items, onItemSelect }) {
const handleItemClick = useCallback((item) => {
onItemSelect(item);
}, [onItemSelect]);
return (
<ul>
{items.map(item => (
<Item key={item.id} item={item} onClick={handleItemClick} />
))}
</ul>
);
}
function Item({ item, onClick }) {
// 使用memo优化子组件
const memoizedClick = useCallback(() => onClick(item), [onClick, item]);
return (
<li onClick={memoizedClick}>
{item.name}
</li>
);
}
计算结果缓存
// ❌ 不推荐:每次渲染都重新计算
function BadComponent({ items, filter }) {
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
const sortedItems = [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
return (
<div>
{sortedItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
// ✅ 推荐:使用useMemo
function GoodComponent({ items, filter }) {
const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);
const sortedItems = useMemo(() => {
return [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
}, [filteredItems]);
return (
<div>
{sortedItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
useRef的高级应用
DOM引用访问
function FocusInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current?.focus();
};
useEffect(() => {
// 组件挂载后自动聚焦
inputRef.current?.focus();
}, []);
return (
<div>
<input ref={inputRef} type="text" placeholder="I'll be focused" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
跨渲染保持值
function Counter() {
const [count, setCount] = useState(0);
const previousCount = useRef(count);
useEffect(() => {
// 监听状态变化
console.log('Count changed from', previousCount.current, 'to', count);
previousCount.current = count;
}, [count]);
return (
<div>
<p>Current: {count}</p>
<p>Previous: {previousCount.current}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
动画控制
function useAnimation() {
const animationRef = useRef(null);
const [isAnimating, setIsAnimating] = useState(false);
const startAnimation = useCallback((callback) => {
if (isAnimating) return;
setIsAnimating(true);
const animate = () => {
if (callback()) {
animationRef.current = requestAnimationFrame(animate);
} else {
setIsAnimating(false);
}
};
animationRef.current = requestAnimationFrame(animate);
}, [isAnimating]);
const stopAnimation = useCallback(() => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
setIsAnimating(false);
}
}, []);
useEffect(() => {
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, []);
return { startAnimation, stopAnimation, isAnimating };
}
// 使用示例
function AnimatedComponent() {
const { startAnimation, stopAnimation, isAnimating } = useAnimation();
const animate = useCallback(() => {
// 动画逻辑
console.log('Animating...');
return false; // 返回false停止动画
}, []);
return (
<div>
<button onClick={() => startAnimation(animate)}>
{isAnimating ? 'Stop' : 'Start'} Animation
</button>
</div>
);
}
自定义Hook开发实战
数据获取Hook
// 基础数据获取Hook
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url, JSON.stringify(options)]);
return { data, loading, error };
}
// 带缓存的数据获取Hook
function useFetchWithCache(url, options = {}) {
const cache = useRef(new Map());
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
// 检查缓存
const cached = cache.current.get(url);
if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) { // 5分钟缓存
setData(cached.data);
return;
}
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
// 缓存结果
cache.current.set(url, {
data: result,
timestamp: Date.now()
});
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url, JSON.stringify(options)]);
const refetch = useCallback(() => {
cache.current.delete(url);
// 重新触发Effect
}, [url]);
return { data, loading, error, refetch };
}
表单处理Hook
// 基础表单Hook
function useForm(initialValues, validationRules = {}) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = useCallback((field, value) => {
setValues(prev => ({ ...prev, [field]: value }));
// 实时验证
if (validationRules[field]) {
const error = validateField(field, value, validationRules[field]);
setErrors(prev => ({ ...prev, [field]: error }));
}
}, [validationRules]);
const handleBlur = useCallback((field) => {
setTouched(prev => ({ ...prev, [field]: true }));
if (validationRules[field]) {
const error = validateField(field, values[field], validationRules[field]);
setErrors(prev => ({ ...prev, [field]: error }));
}
}, [values, validationRules]);
const validateField = useCallback((field, value, rules) => {
for (const rule of rules) {
if (!rule.test(value)) {
return rule.message;
}
}
return '';
}, []);
const validateForm = useCallback(() => {
const newErrors = {};
let isValid = true;
Object.keys(validationRules).forEach(field => {
const error = validateField(field, values[field], validationRules[field]);
if (error) {
isValid = false;
newErrors[field] = error;
}
});
setErrors(newErrors);
return isValid;
}, [values, validationRules, validateField]);
const resetForm = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
}, [initialValues]);
const isFormValid = Object.values(errors).every(error => !error);
return {
values,
errors,
touched,
handleChange,
handleBlur,
validateForm,
resetForm,
isFormValid
};
}
// 高级表单Hook - 支持数组字段
function useArrayForm(initialValues) {
const [values, setValues] = useState(initialValues);
const addField = useCallback((arrayName, item) => {
setValues(prev => ({
...prev,
[arrayName]: [...(prev[arrayName] || []), item]
}));
}, []);
const removeField = useCallback((arrayName, index) => {
setValues(prev => ({
...prev,
[arrayName]: prev[arrayName].filter((_, i) => i !== index)
}));
}, []);
const updateField = useCallback((arrayName, index, updatedItem) => {
setValues(prev => ({
...prev,
[arrayName]: prev[arrayName].map((item, i) =>
i === index ? updatedItem : item
)
}));
}, []);
return {
values,
addField,
removeField,
updateField
};
}
窗口尺寸Hook
function useWindowSize() {
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 windowSize;
}
// 带防抖的窗口尺寸Hook
function useDebounceWindowSize(delay = 250) {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
let timeoutId;
const handleResize = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
}, delay);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [delay]);
return windowSize;
}
复杂业务场景应用
实时搜索功能
function useSearch(items, searchKey = 'name') {
const [searchTerm, setSearchTerm] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
const [debouncedSearch, setDebouncedSearch] = useState('');
// 防抖搜索
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearch(searchTerm);
}, 300);
return () => {
clearTimeout(handler);
};
}, [searchTerm]);
// 过滤逻辑
useEffect(() => {
if (!debouncedSearch) {
setFilteredItems(items);
return;
}
const filtered = items.filter(item =>
item[searchKey].toLowerCase().includes(debouncedSearch.toLowerCase())
);
setFilteredItems(filtered);
}, [debouncedSearch, items, searchKey]);
const clearSearch = useCallback(() => {
setSearchTerm('');
setDebouncedSearch('');
setFilteredItems(items);
}, [items]);
return {
searchTerm,
setSearchTerm,
filteredItems,
clearSearch
};
}
// 使用示例
function ProductList({ products }) {
const { searchTerm, setSearchTerm, filteredItems } = useSearch(products, 'title');
return (
<div>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search products..."
/>
<ul>
{filteredItems.map(product => (
<li key={product.id}>{product.title}</li>
))}
</ul>
</div>
);
}
数据持久化Hook
function useLocalStorage(key, initialValue) {
// 从localStorage获取初始值
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;
}
});
// 更新localStorage
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
};
// 删除localStorage项
const removeValue = () => {
try {
setStoredValue(initialValue);
window.localStorage.removeItem(key);
} catch (error) {
console.error(`Error removing localStorage key "${key}":`, error);
}
};
return [storedValue, setValue, removeValue];
}
// 使用示例
function ThemeToggle() {
const [theme, setTheme, removeTheme] = useLocalStorage('theme', 'light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light
评论 (0)