React 应用的性能优化需要从多个角度考虑:组件渲染、数据获取、代码分割等。本篇将介绍实用的性能优化技巧。
性能优化的基本原则
1. 测量优先
在优化之前,先使用工具测量性能瓶颈:
- React DevTools Profiler
- Chrome DevTools Performance
- Lighthouse
2. 避免过早优化
先确保功能正确,再考虑性能优化。
3. 关注用户体验
优化应该以提升用户体验为目标,而不是单纯的数字指标。
React DevTools Profiler 使用
Profiler 是 React 内置的性能分析工具。
import { Profiler } from 'react';
function onRenderCallback(
id: string,
phase: 'mount' | 'update',
actualDuration: number,
baseDuration: number,
startTime: number,
commitTime: number
) {
console.log(`组件 ${id} 在 ${phase} 阶段耗时 ${actualDuration}ms`);
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Header />
<Main />
<Footer />
</Profiler>
);
}组件渲染优化
1. React.memo
防止不必要的重新渲染:
import { memo } from 'react';
interface UserCardProps {
user: User;
onEdit: (id: number) => void;
}
const UserCard = memo<UserCardProps>(({ user, onEdit }) => {
console.log(`UserCard ${user.id} 重新渲染`);
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onEdit(user.id)}>编辑</button>
</div>
);
});
// 使用示例
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const handleEdit = useCallback((id: number) => {
console.log('编辑用户:', id);
}, []);
return (
<div>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onEdit={handleEdit}
/>
))}
</div>
);
}2. useMemo 优化计算
缓存昂贵的计算结果:
import { useMemo } from 'react';
function ExpensiveComponent({ data, filter }) {
// 缓存过滤和排序的结果
const processedData = useMemo(() => {
console.log('重新计算数据...');
return data
.filter(item => item.name.includes(filter))
.sort((a, b) => a.name.localeCompare(b.name));
}, [data, filter]);
return (
<div>
{processedData.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}3. useCallback 优化函数
缓存函数引用:
import { useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// 缓存函数引用
const handleClick = useCallback(() => {
console.log('按钮被点击');
}, []);
const handleSubmit = useCallback((data) => {
console.log('提交数据:', data);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>
增加计数
</button>
<ChildComponent
onButtonClick={handleClick}
onSubmit={handleSubmit}
/>
</div>
);
}
const ChildComponent = memo(({ onButtonClick, onSubmit }) => {
console.log('ChildComponent 重新渲染');
return (
<div>
<button onClick={onButtonClick}>子组件按钮</button>
</div>
);
});列表渲染优化
1. 虚拟化长列表
使用 react-window 或 react-virtualized:
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
<div className="list-item">
<h3>{items[index].name}</h3>
<p>{items[index].description}</p>
</div>
</div>
);
return (
<List
height={400}
itemCount={items.length}
itemSize={80}
width="100%"
>
{Row}
</List>
);
}
// 使用示例
function App() {
const [items, setItems] = useState([]);
useEffect(() => {
// 模拟大量数据
const mockItems = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `项目 ${i}`,
description: `这是项目 ${i} 的描述`
}));
setItems(mockItems);
}, []);
return <VirtualizedList items={items} />;
}2. 分页加载
function PaginatedList() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const loadMore = useCallback(async () => {
if (loading || !hasMore) return;
setLoading(true);
try {
const response = await fetch(`/api/items?page=${page}&limit=20`);
const newItems = await response.json();
setItems(prev => [...prev, ...newItems]);
setPage(prev => prev + 1);
setHasMore(newItems.length === 20);
} catch (error) {
console.error('加载失败:', error);
} finally {
setLoading(false);
}
}, [page, loading, hasMore]);
return (
<div>
<div className="items-list">
{items.map(item => (
<div key={item.id} className="item">
{item.name}
</div>
))}
</div>
{hasMore && (
<button
onClick={loadMore}
disabled={loading}
className="load-more-btn"
>
{loading ? '加载中...' : '加载更多'}
</button>
)}
</div>
);
}数据获取优化
1. 使用 TanStack Query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function UserProfile({ userId }) {
const queryClient = useQueryClient();
// 获取用户数据
const { data: user, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
staleTime: 5 * 60 * 1000, // 5分钟内数据不重新获取
cacheTime: 10 * 60 * 1000, // 缓存10分钟
});
// 更新用户数据
const updateUserMutation = useMutation({
mutationFn: (userData) =>
fetch(`/api/users/${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData),
}).then(res => res.json()),
onSuccess: (updatedUser) => {
// 更新缓存
queryClient.setQueryData(['user', userId], updatedUser);
},
});
if (isLoading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<button
onClick={() => updateUserMutation.mutate({ name: '新名字' })}
disabled={updateUserMutation.isPending}
>
{updateUserMutation.isPending ? '更新中...' : '更新'}
</button>
</div>
);
}2. 预加载数据
function App() {
const queryClient = useQueryClient();
// 预加载用户数据
const prefetchUser = (userId) => {
queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
});
};
return (
<div>
<nav>
<Link
to="/users/1"
onMouseEnter={() => prefetchUser(1)}
>
用户1
</Link>
<Link
to="/users/2"
onMouseEnter={() => prefetchUser(2)}
>
用户2
</Link>
</nav>
<Outlet />
</div>
);
}代码分割和懒加载
1. React.lazy
import { lazy, Suspense } from 'react';
// 懒加载组件
const UserDashboard = lazy(() => import('./UserDashboard'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function App() {
const [userRole, setUserRole] = useState('user');
return (
<div>
<Suspense fallback={<div>加载中...</div>}>
{userRole === 'admin' ? <AdminPanel /> : <UserDashboard />}
</Suspense>
</div>
);
}2. 路由级别的代码分割
import { lazy } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
function App() {
return (
<Suspense fallback={<div>页面加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
);
}Bundle 分析和优化
1. 使用 webpack-bundle-analyzer
# 安装
npm install --save-dev webpack-bundle-analyzer
# 分析构建结果
npm run build
npx webpack-bundle-analyzer build/static/js/*.js2. 优化依赖
// 避免导入整个库
// 错误
import _ from 'lodash';
// 正确
import debounce from 'lodash/debounce';
// 使用动态导入
const loadHeavyLibrary = async () => {
const { default: heavyLibrary } = await import('./heavyLibrary');
return heavyLibrary;
};内存泄漏预防
1. 清理事件监听器
function EventListenerComponent() {
useEffect(() => {
const handleResize = () => {
console.log('窗口大小改变');
};
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
}2. 清理定时器
function TimerComponent() {
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
}3. 清理异步操作
function AsyncComponent() {
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
const data = await fetch('/api/data');
if (isMounted) {
// 只有在组件仍然挂载时才更新状态
setData(await data.json());
}
} catch (error) {
if (isMounted) {
setError(error);
}
}
};
fetchData();
return () => {
isMounted = false;
};
}, []);
}性能监控
1. 使用 React Profiler API
import { Profiler } from 'react';
function App() {
const handleProfilerRender = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) => {
// 发送性能数据到分析服务
if (actualDuration > 16) { // 超过一帧的时间
console.warn(`组件 ${id} 渲染时间过长: ${actualDuration}ms`);
}
};
return (
<Profiler id="App" onRender={handleProfilerRender}>
<Header />
<Main />
<Footer />
</Profiler>
);
}2. 自定义性能 Hook
function usePerformanceMonitor(componentName) {
useEffect(() => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
const duration = endTime - startTime;
if (duration > 100) {
console.warn(`${componentName} 挂载时间过长: ${duration}ms`);
}
};
});
}
function ExpensiveComponent() {
usePerformanceMonitor('ExpensiveComponent');
return <div>复杂组件</div>;
}总结
- 测量优先:使用 Profiler 和性能工具识别瓶颈
- 组件优化:合理使用 memo、useMemo、useCallback
- 列表优化:虚拟化长列表,实现分页加载
- 数据优化:使用 TanStack Query 管理服务端状态
- 代码分割:按需加载,减少初始包大小
- 内存管理:及时清理事件监听器和定时器
- 持续监控:建立性能监控体系
掌握这些优化技巧,你就能构建出高性能的 React 应用。