📖 核心概念
- React渲染机制与Reconciliation
- React.memo与组件记忆化
- 虚拟化长列表
- 代码分割与懒加载
- 图片优化与资源加载
- React DevTools Profiler
💻 代码实现
性能优化技巧
✅ tsx
import { memo, useMemo, useCallback, lazy, Suspense, useState, useRef, useEffect } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
// ===== 1. React.memo - 避免不必要的重渲染 =====
interface ExpensiveItemProps {
id: string;
name: string;
onSelect: (id: string) => void;
}
const ExpensiveItem = memo(function ExpensiveItem({ id, name, onSelect }: ExpensiveItemProps) {
console.log(\`渲染: \${name}\`);
return (
<div onClick={() => onSelect(id)} className="item">
{name}
</div>
);
}, (prev, next) => prev.id === next.id && prev.name === next.name);
// ===== 2. 虚拟列表 - 渲染10000+项无压力 =====
function VirtualList({ items }: { items: { id: string; name: string }[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 48,
overscan: 5, // 预渲染5个
});
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: \`\${virtualizer.getTotalSize()}px\`, position: 'relative' }}>
{virtualizer.getVirtualItems().map(vItem => (
<div
key={vItem.key}
style={{
position: 'absolute',
top: 0,
transform: \`translateY(\${vItem.start}px)\`,
height: \`\${vItem.size}px\`,
}}
>
{items[vItem.index].name}
</div>
))}
</div>
</div>
);
}
// ===== 3. 代码分割 =====
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const Chart = lazy(() => import('./Chart'));
function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<h1>控制台</h1>
<Suspense fallback={<Skeleton />}>
<HeavyComponent />
</Suspense>
<button onClick={() => setShowChart(true)}>显示图表</button>
{showChart && (
<Suspense fallback={<div>图表加载中...</div>}>
<Chart />
</Suspense>
)}
</div>
);
}
// ===== 4. 图片优化 =====
function OptimizedImage({ src, alt, width, height }: ImageProps) {
const [loaded, setLoaded] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && imgRef.current) {
imgRef.current.src = imgRef.current.dataset.src!;
observer.unobserve(entry.target);
}
});
if (imgRef.current) observer.observe(imgRef.current);
return () => observer.disconnect();
}, []);
return (
<div style={{ width, height, background: '#1e293b' }}>
{!loaded && <Skeleton />}
<img
ref={imgRef}
data-src={src}
alt={alt}
onLoad={() => setLoaded(true)}
style={{ opacity: loaded ? 1 : 0, transition: 'opacity 0.3s' }}
loading="lazy"
decoding="async"
/>
</div>
);
}
性能分析与调试
✅ tsx
/**
* React DevTools Profiler 使用指南:
* 1. 安装React DevTools浏览器扩展
* 2. 打开Profiler标签页
* 3. 点击录制按钮
* 4. 操作你的应用
* 5. 停止录制,分析渲染数据
*
* 关键指标:
* - Render time: 组件渲染耗时
* - Why did this render?: 渲染原因
* - Flame chart: 渲染树可视化
*/
// 自定义性能监控Hook
function useRenderCount(componentName: string) {
const count = useRef(0);
count.current++;
useEffect(() => {
if (count.current > 1) {
console.log(\`[\${componentName}] 渲染次数: \${count.current}\`);
}
});
}
// 状态下推 - 避免整棵树重渲染
// ❌ 不好:所有子组件都会重渲染
function BadList() {
const [selectedId, setSelectedId] = useState<string | null>(null);
return items.map(item => (
<Item
key={item.id}
item={item}
selected={selectedId === item.id}
onSelect={setSelectedId}
/>
));
}
// ✅ 好:每个Item管理自己的选中状态
function GoodItem({ item }: { item: Item }) {
const [selected, setSelected] = useState(false);
return <div onClick={() => setSelected(!selected)}>...</div>;
}
// 使用React.memo + 稳定引用
function Parent() {
const [count, setCount] = useState(0);
// useCallback保持函数引用稳定
const handleSelect = useCallback((id: string) => {
// 处理选择逻辑
}, []); // 无依赖,引用永不变
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<MemoizedChild onSelect={handleSelect} />
</div>
);
}
// 批量状态更新 (React 18自动批处理)
function BatchUpdates() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
// React 18: 只触发一次渲染
const handleClick = () => {
setA(a + 1);
setB(b + 1);
};
// 在异步代码中也自动批处理 (React 18新特性)
const handleAsync = async () => {
const data = await fetchData();
setA(data.a); // 不会立即渲染
setB(data.b); // 不会立即渲染
// 两次setState合并为一次渲染
};
}
🎯 练习任务
- 1 使用React DevTools Profiler分析并优化一个慢组件
- 2 实现虚拟列表,渲染10000条数据保持流畅
- 3 对比React.memo、useMemo、useCallback的使用场景
- 4 优化一个实际项目的首屏加载性能(LCP < 2s)
🏆 成就解锁
性能优化师 — 掌握React性能优化技巧,打造丝滑的用户体验