Hooks最佳实践 #
一、Hooks规则 #
1.1 只在最顶层调用Hook #
javascript
// ❌ 错误:在条件语句中调用
function Component({ isLoggedIn }) {
if (isLoggedIn) {
const [user, setUser] = useState(null); // 错误
}
return <div>...</div>;
}
// ✅ 正确:在顶层调用
function Component({ isLoggedIn }) {
const [user, setUser] = useState(null);
if (isLoggedIn) {
// 使用user
}
return <div>...</div>;
}
1.2 只在React函数中调用Hook #
javascript
// ✅ 在函数组件中调用
function Component() {
const [state, setState] = useState(0);
}
// ✅ 在自定义Hook中调用
function useCustomHook() {
const [state, setState] = useState(0);
}
// ❌ 不要在普通函数中调用
function handleClick() {
const [state, setState] = useState(0); // 错误
}
// ❌ 不要在类组件中调用
class Component extends React.Component {
render() {
const [state, setState] = useState(0); // 错误
}
}
1.3 使用ESLint插件 #
javascript
// 安装
// npm install eslint-plugin-react-hooks --save-dev
// .eslintrc配置
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
二、依赖管理 #
2.1 正确设置依赖 #
javascript
// ❌ 缺少依赖
useEffect(() => {
fetchData(userId);
}, []); // userId变化时不会重新执行
// ✅ 包含所有依赖
useEffect(() => {
fetchData(userId);
}, [userId]);
2.2 函数依赖 #
javascript
// ❌ 函数作为依赖会导致频繁执行
function Component() {
const fetchData = () => {
console.log('fetching');
};
useEffect(() => {
fetchData();
}, [fetchData]); // fetchData每次渲染都是新引用
}
// ✅ 使用useCallback
function Component() {
const fetchData = useCallback(() => {
console.log('fetching');
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
}
// ✅ 或者将函数移到useEffect内部
function Component() {
useEffect(() => {
const fetchData = () => {
console.log('fetching');
};
fetchData();
}, []);
}
2.3 对象依赖 #
javascript
// ❌ 对象作为依赖会导致频繁执行
function Component({ options }) {
useEffect(() => {
doSomething(options);
}, [options]); // options每次渲染都是新对象
}
// ✅ 使用useMemo
function Component({ options }) {
const memoizedOptions = useMemo(() => options, [JSON.stringify(options)]);
useEffect(() => {
doSomething(memoizedOptions);
}, [memoizedOptions]);
}
// ✅ 或者解构依赖
function Component({ options }) {
const { a, b } = options;
useEffect(() => {
doSomething({ a, b });
}, [a, b]);
}
三、性能优化 #
3.1 使用useMemo缓存计算 #
javascript
// ❌ 每次渲染都重新计算
function Component({ list }) {
const sortedList = list.sort((a, b) => a - b);
return <List data={sortedList} />;
}
// ✅ 使用useMemo缓存
function Component({ list }) {
const sortedList = useMemo(
() => [...list].sort((a, b) => a - b),
[list]
);
return <List data={sortedList} />;
}
3.2 使用useCallback缓存函数 #
javascript
// ❌ 每次渲染创建新函数
function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log('clicked');
};
return <Child onClick={handleClick} />;
}
// ✅ 使用useCallback
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <Child onClick={handleClick} />;
}
3.3 合理使用React.memo #
javascript
const Child = React.memo(function Child({ onClick, data }) {
console.log('Child render');
return <button onClick={onClick}>{data}</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
const data = useMemo(() => 'Button', []);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<Child onClick={handleClick} data={data} />
</div>
);
}
3.4 避免过度优化 #
javascript
// ❌ 过度优化:简单计算不需要useMemo
const doubled = useMemo(() => count * 2, [count]);
// ✅ 简单计算直接计算
const doubled = count * 2;
// ✅ 复杂计算使用useMemo
const sortedList = useMemo(() => {
return [...list].sort((a, b) => {
// 复杂排序逻辑
});
}, [list]);
四、状态管理 #
4.1 状态最小化 #
javascript
// ❌ 冗余状态
function Component() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState(''); // 冗余
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
}
// ✅ 计算派生状态
function Component() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = `${firstName} ${lastName}`;
}
4.2 状态就近原则 #
javascript
// ✅ 状态放在使用它的最近父组件
function TodoItem({ todo }) {
const [isEditing, setIsEditing] = useState(false);
return (
<div>
{isEditing ? <EditForm /> : <TodoDisplay />}
</div>
);
}
4.3 使用函数式更新 #
javascript
// ❌ 依赖旧状态
const increment = () => {
setCount(count + 1);
setCount(count + 1);
// 结果:count只增加1
};
// ✅ 使用函数式更新
const increment = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 结果:count增加2
};
五、副作用处理 #
5.1 及时清理副作用 #
javascript
// ✅ 清理定时器
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(timer);
}, []);
// ✅ 清理事件监听
useEffect(() => {
const handleResize = () => {
console.log('resize');
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
5.2 处理竞态条件 #
javascript
// ✅ 使用标志位
useEffect(() => {
let isMounted = true;
fetch(url)
.then(res => res.json())
.then(data => {
if (isMounted) {
setData(data);
}
});
return () => {
isMounted = false;
};
}, [url]);
// ✅ 使用AbortController
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') {
setError(err);
}
});
return () => controller.abort();
}, [url]);
5.3 避免在渲染中产生副作用 #
javascript
// ❌ 在渲染中产生副作用
function Component() {
const [count, setCount] = useState(0);
if (count > 10) {
setCount(0); // 副作用
}
return <div>{count}</div>;
}
// ✅ 使用useEffect
function Component() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count > 10) {
setCount(0);
}
}, [count]);
return <div>{count}</div>;
}
六、常见陷阱 #
6.1 闭包陷阱 #
javascript
// ❌ 闭包陷阱
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 永远是0
}, 1000);
return () => clearInterval(timer);
}, []);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// ✅ 使用ref解决
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
const timer = setInterval(() => {
console.log(countRef.current);
}, 1000);
return () => clearInterval(timer);
}, []);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
6.2 无限循环 #
javascript
// ❌ 无限循环
function Component() {
const [data, setData] = useState([]);
useEffect(() => {
setData([...data, newItem]); // 触发重新渲染
}, [data]); // data变化又触发useEffect
}
// ✅ 使用函数式更新
function Component() {
const [data, setData] = useState([]);
useEffect(() => {
setData(prev => [...prev, newItem]);
}, []); // 只执行一次
}
6.3 不正确的初始化 #
javascript
// ❌ 每次渲染都执行昂贵计算
const [state, setState] = useState(expensiveComputation());
// ✅ 使用惰性初始化
const [state, setState] = useState(() => expensiveComputation());
七、代码组织 #
7.1 相关状态放在一起 #
javascript
// ✅ 相关状态组合
function Form() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
}
// 或使用useReducer
function Form() {
const [state, dispatch] = useReducer(formReducer, initialState);
}
7.2 抽取自定义Hook #
javascript
// ❌ 组件逻辑复杂
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 获取用户逻辑...
}, [userId]);
// 其他逻辑...
}
// ✅ 抽取自定义Hook
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 获取用户逻辑...
}, [userId]);
return { user, loading, error };
}
function UserProfile({ userId }) {
const { user, loading, error } = useUser(userId);
if (loading) return <Loading />;
if (error) return <Error error={error} />;
return <UserCard user={user} />;
}
八、最佳实践清单 #
8.1 规则清单 #
| 规则 | 说明 |
|---|---|
| 顶层调用 | 不在条件、循环中调用Hook |
| React函数 | 只在组件或自定义Hook中调用 |
| 依赖完整 | useEffect依赖数组完整 |
8.2 性能清单 #
| 优化 | 说明 |
|---|---|
| useMemo | 缓存复杂计算结果 |
| useCallback | 缓存传递给子组件的函数 |
| React.memo | 避免子组件不必要渲染 |
| 惰性初始化 | 避免重复执行昂贵计算 |
8.3 状态清单 #
| 实践 | 说明 |
|---|---|
| 最小化 | 避免冗余状态 |
| 就近原则 | 状态放在最近父组件 |
| 函数式更新 | 避免依赖旧状态 |
九、总结 #
| 类别 | 要点 |
|---|---|
| 规则 | 顶层调用、React函数中调用 |
| 依赖 | 正确设置、避免遗漏 |
| 性能 | useMemo、useCallback、React.memo |
| 状态 | 最小化、就近原则、函数式更新 |
| 副作用 | 及时清理、处理竞态 |
核心原则:
- 遵循 Hooks 规则
- 正确管理依赖
- 合理优化性能
- 保持代码简洁
最后更新:2026-03-26