React Hooks 实战指南
为什么需要 Hooks
在 Hooks 出现之前,React 组件分为类组件和函数组件。类组件有完整的生命周期方法,但存在几个问题:
- 逻辑难以复用:高阶组件和 Render Props 会导致嵌套地狱
- 复杂组件难以理解:生命周期中混入不相关的逻辑
- this 指向困扰:需要手动绑定事件处理函数
Hooks 让函数组件也能使用状态和生命周期特性,逻辑复用变得简单。
useState
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount((prev) => prev - 1)}>减少</button>
</div>
);
}
注意事项:
- 状态更新是异步的,多次调用会批量处理
- 使用函数式更新可避免闭包陷阱:
setCount(prev => prev + 1) - 对象状态需要手动合并(useState 不会自动合并)
useEffect
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => {
if (!cancelled) {
setUser(data);
setLoading(false);
}
});
return () => {
cancelled = true; // 清理函数:避免竞态条件
};
}, [userId]); // 仅在 userId 变化时重新执行
if (loading) return <div>加载中...</div>;
return <div>{user?.name}</div>;
}
依赖数组规则:
- 空数组
[]:仅在挂载时执行,卸载时清理 - 有依赖
[id]:依赖变化时重新执行 - 不传参:每次渲染都执行(慎用)
useContext
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={`toolbar-${theme}`}>工具栏</div>;
}
useRef
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
}
useRef 保存的值在组件整个生命周期内保持不变,且不会触发重新渲染。适合存储定时器 ID、DOM 引用等可变值。
useMemo 与 useCallback
import { useMemo, useCallback } from 'react';
function ExpenseList({ items, taxRate }) {
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price * (1 + taxRate), 0);
}, [items, taxRate]);
const handleItemClick = useCallback((id) => {
console.log('点击了项目:', id);
}, [items]);
return (
<div>
<p>总计: {total}</p>
{items.map((item) => (
<div key={item.id} onClick={() => handleItemClick(item.id)}>
{item.name}
</div>
))}
</div>
);
}
自定义 Hooks
自定义 Hook 是 Hooks 逻辑复用的核心。
// useDebounce.ts
import { useState, useEffect } from 'react';
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// 使用
function SearchPage() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
fetch(`/api/search?q=${debouncedQuery}`);
}
}, [debouncedQuery]);
return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
Hooks 使用规则
- 只在顶层调用 Hooks:不要在条件、循环或嵌套函数中调用
- 只在 React 函数组件或自定义 Hook 中调用
- 自定义 Hook 必须以 use 开头
总结
Hooks 让 React 组件的逻辑复用和状态管理变得前所未有的简单。推荐从 useState、useEffect 开始,逐步学习 useReducer、useContext 等高级 Hook,通过自定义 Hook 提取业务逻辑,保持组件的简洁和可测试性。