引言
自 React 16.8 引入 Hooks 以来,函数组件已成为主流。但滥用或误用 Hooks 会导致性能问题和难以维护的代码。本文分享 React Hooks 的核心最佳实践。
一、useState 使用技巧
1.1 状态拆分原则
// ❌ 不推荐:相关度低的状态放在一起
const [user, setUser] = useState({ name: '', age: 0, isLoggedIn: false });
// ✅ 推荐:按逻辑拆分
const [userName, setUserName] = useState('');
const [userAge, setUserAge] = useState(0);
const [isLoggedIn, setIsLoggedIn] = useState(false);
1.2 函数式更新
// ❌ 可能获取到旧状态
setCount(count + 1);
// ✅ 使用函数式更新(特别是异步操作时)
setCount(prevCount => prevCount + 1);
1.3 状态初始化优化
// ❌ 每次渲染都执行
const [data, setData] = useState(expensiveComputation());
// ✅ 惰性初始化(只执行一次)
const [data, setData] = useState(() => expensiveComputation());
二、useEffect 正确用法
2.1 依赖数组管理
// ✅ 明确列出所有依赖
useEffect(() => {
fetchData(userId);
}, [userId]); // 不要省略依赖!
// ✅ 使用 ESLint 插件自动检查
// eslint-plugin-react-hooks
2.2 清理副作用
useEffect(() => {
const subscription = api.subscribe();
// 清理函数
return () => {
subscription.unsubscribe();
};
}, []);
2.3 避免无限循环
// ❌ 依赖对象导致无限循环
const [config, setConfig] = useState({});
useEffect(() => {
setConfig({ ...config, updated: true });
}, [config]); // config 每次都是新引用
// ✅ 使用状态更新函数或稳定引用
useEffect(() => {
setConfig(prev => ({ ...prev, updated: true }));
}, []);
三、useMemo 和 useCallback
3.1 何时使用 useMemo
// ✅ 计算密集型操作
const filteredList = useMemo(() => {
return list.filter(item => item.status === 'active');
}, [list]);
// ❌ 简单计算不需要
const doubled = useMemo(() => count * 2, [count]); // 过度优化
3.2 useCallback 防止子组件重渲染
// 父组件
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return ;
// 子组件(需要 React.memo)
const Child = React.memo(({ onClick }) => {
return ;
});
四、自定义 Hooks
4.1 提取可复用逻辑
// useLocalStorage.js
import { useState, useEffect } from 'react';
export function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
const setValue = (value) => {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
};
return [storedValue, setValue];
}
// 使用
const [theme, setTheme] = useLocalStorage('theme', 'light');
4.2 数据获取 Hook
// useFetch.js
import { useState, useEffect } from 'react';
export function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const response = await fetch(url, { signal: controller.signal });
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err);
}
} finally {
setLoading(false);
}
}
fetchData();
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
五、性能优化实践
5.1 使用 React.memo
const ExpensiveComponent = React.memo(({ data }) => {
return (
{data.map(item => )}
);
});
5.2 代码分割
// 懒加载组件
const Dashboard = lazy(() => import('./Dashboard'));
function App() {
return (
}>
);
}
六、常见陷阱与解决方案
6.1 Stale Closure 问题
// ❌ 闭包捕获旧值
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log(count); // 永远是 0
}, 1000);
return () => clearInterval(id);
}, []);
}
// ✅ 使用 ref 或函数式更新
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
});
useEffect(() => {
const id = setInterval(() => {
console.log(countRef.current); // 最新值
}, 1000);
return () => clearInterval(id);
}, []);
}
6.2 条件调用 Hooks
// ❌ 违反 Hooks 规则
if (isLoggedIn) {
useEffect(() => { ... });
}
// ✅ 条件逻辑放在 Hook 内部
useEffect(() => {
if (isLoggedIn) {
// ...
}
}, [isLoggedIn]);
七、调试技巧
7.1 使用 React DevTools
- Components 面板:查看组件树和 Props/State
- Profiler 面板:分析渲染性能
- 高亮更新:识别不必要的重渲染
7.2 自定义 Hook 调试
import { useEffect } from 'react';
export function useDebug(value, label) {
useEffect(() => {
console.log(`[${label}]`, value);
}, [value, label]);
}
// 使用
useDebug(count, 'count');
useDebug(data, 'data');
总结
React Hooks 最佳实践核心要点:
- 保持 Hook 调用顺序一致
- 正确管理依赖数组
- 按需使用 useMemo/useCallback
- 提取可复用逻辑到自定义 Hooks
- 注意清理副作用
- 使用 React DevTools 调试
记住: premature optimization is the root of all evil。先写出正确的代码,再根据性能分析进行优化。
文章评论