背景
最近接手了一个性能问题严重的项目,Lighthouse 评分只有 70 分,首屏加载时间超过 5 秒。经过一个月的优化,最终将评分提升到 95 分,首屏加载时间降到 1.2 秒。本文将分享整个优化过程。
性能分析
初始状态
使用 Chrome DevTools 和 Lighthouse 进行初步分析,发现以下问题:
- 包体积过大:主包达到 2.5MB
- 未优化的图片:大量未压缩的高清图片
- 阻塞渲染的资源:多个同步加载的第三方脚本
- 内存泄漏:长时间使用后内存占用持续增长
性能指标
初始性能指标:
- FCP (First Contentful Paint): 3.2s
- LCP (Largest Contentful Paint): 5.1s
- TTI (Time to Interactive): 7.8s
- CLS (Cumulative Layout Shift): 0.25
优化策略
1. 代码分割和懒加载
首先对路由进行代码分割:
// 使用 React.lazy 进行路由级别的代码分割
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Analytics = lazy(() => import('./pages/Analytics'))
const Settings = lazy(() => import('./pages/Settings'))
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
)
}
对大型第三方库进行按需加载:
// 只在需要时加载 Chart.js
async function loadChartLibrary() {
const { Chart } = await import('chart.js/auto')
return Chart
}
function ChartComponent({ data }) {
useEffect(() => {
loadChartLibrary().then(Chart => {
// 使用 Chart.js
})
}, [])
}
2. 图片优化
实施多维度的图片优化策略:
// 使用 next/image 自动优化图片
import Image from 'next/image'
function OptimizedImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={800}
height={600}
loading="lazy"
placeholder="blur"
blurDataURL={generateBlurDataURL(src)}
/>
)
}
// 根据设备提供不同尺寸的图片
function ResponsiveImage({ src, alt }) {
return (
<picture>
<source
media="(max-width: 640px)"
srcSet={`${src}?w=640&q=75`}
/>
<source
media="(max-width: 1024px)"
srcSet={`${src}?w=1024&q=75`}
/>
<img
src={`${src}?w=1920&q=75`}
alt={alt}
loading="lazy"
/>
</picture>
)
}
3. 资源加载优化
优化关键资源的加载顺序:
<!-- 预连接到关键域名 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://api.example.com">
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<!-- 延迟加载非关键脚本 -->
<script src="/js/analytics.js" defer></script>
<script src="/js/chat-widget.js" async></script>
4. 运行时性能优化
优化 React 组件的渲染性能:
// 使用 memo 避免不必要的重渲染
const ExpensiveComponent = memo(({ data }) => {
return <ComplexVisualization data={data} />
}, (prevProps, nextProps) => {
// 自定义比较逻辑
return prevProps.data.id === nextProps.data.id
})
// 使用 useMemo 缓存计算结果
function DataProcessor({ rawData }) {
const processedData = useMemo(() => {
return expensiveProcessing(rawData)
}, [rawData])
return <DataDisplay data={processedData} />
}
// 使用 useCallback 避免函数重创建
function SearchBar({ onSearch }) {
const [query, setQuery] = useState('')
const debouncedSearch = useCallback(
debounce((value) => onSearch(value), 300),
[onSearch]
)
return (
<input
value={query}
onChange={(e) => {
setQuery(e.target.value)
debouncedSearch(e.target.value)
}}
/>
)
}
5. 缓存策略
实施多层缓存策略:
// Service Worker 缓存策略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// 缓存优先,网络回退
return response || fetch(event.request).then((response) => {
// 缓存新资源
if (response.status === 200) {
const responseClone = response.clone()
caches.open('v1').then((cache) => {
cache.put(event.request, responseClone)
})
}
return response
})
})
)
})
// HTTP 缓存头配置
app.use((req, res, next) => {
// 静态资源长期缓存
if (req.url.match(/\.(js|css|jpg|png|gif|ico|woff2)$/)) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
}
// API 响应短期缓存
else if (req.url.startsWith('/api/')) {
res.setHeader('Cache-Control', 'private, max-age=300')
}
next()
})
6. 内存泄漏修复
定位并修复内存泄漏:
// 错误示例:忘记清理事件监听器
function LeakyComponent() {
useEffect(() => {
const handler = () => console.log('scroll')
window.addEventListener('scroll', handler)
// 忘记清理!
}, [])
}
// 正确示例:清理副作用
function FixedComponent() {
useEffect(() => {
const handler = () => console.log('scroll')
window.addEventListener('scroll', handler)
return () => {
window.removeEventListener('scroll', handler)
}
}, [])
}
// 使用 WeakMap 避免内存泄漏
const cache = new WeakMap()
function getCachedData(obj) {
if (cache.has(obj)) {
return cache.get(obj)
}
const data = processObject(obj)
cache.set(obj, data)
return data
}
优化结果
经过一个月的优化,各项指标显著提升:
性能指标对比
指标 | 优化前 | 优化后 | 提升 |
---|---|---|---|
FCP | 3.2s | 0.8s | 75% |
LCP | 5.1s | 1.2s | 76% |
TTI | 7.8s | 2.1s | 73% |
CLS | 0.25 | 0.02 | 92% |
包体积 | 2.5MB | 780KB | 69% |
用户体验提升
- 跳出率降低 45%:用户更愿意等待页面加载
- 转化率提升 23%:更快的交互响应提高了用户满意度
- 移动端体验改善:在 3G 网络下也能流畅使用
持续监控
建立性能监控体系:
// 使用 Performance Observer API 监控性能
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 上报性能数据
analytics.track('performance', {
name: entry.name,
duration: entry.duration,
startTime: entry.startTime
})
}
})
observer.observe({ entryTypes: ['navigation', 'resource', 'paint'] })
// 监控长任务
const taskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn('Long task detected:', entry)
}
}
})
taskObserver.observe({ entryTypes: ['longtask'] })
经验总结
- 先测量后优化:使用工具准确定位问题,避免盲目优化
- 关注用户体验:性能优化的最终目标是提升用户体验
- 渐进式优化:一次只优化一个方面,便于评估效果
- 自动化监控:建立监控体系,防止性能退化
- 团队协作:制定性能预算,让整个团队关注性能
工具推荐
- 分析工具:Lighthouse、WebPageTest、Chrome DevTools
- 监控服务:Sentry、DataDog、New Relic
- 优化工具:Webpack Bundle Analyzer、source-map-explorer
- 图片优化:Squoosh、ImageOptim、TinyPNG
性能优化是一个持续的过程,需要在开发的每个阶段都保持关注。希望这些经验能帮助你优化自己的项目!