"这个列表页怎么这么卡?"产品经理皱着眉头看着我。作为一个接手海外电商项目的前端开发者,我深知性能问题的重要性。特别是在东南亚市场,很多用户使用的是中低端手机,网络条件也不太理想。
最近一个月,我带领团队对整个React应用进行了一次全面的性能优化,不仅解决了性能问题,还总结出了一套可复用的优化方案。今天就来分享这个过程中的实战经验。
性能问题分析
首先,我们用 Chrome DevTools 和 React DevTools 对应用进行了全面的性能分析。问题主要集中在这几个方面:
- 列表页面首次加载需要3秒以上
- 切换tab时明显卡顿
- 输入搜索关键词时,界面响应延迟
- 滚动大量商品时,帧率明显下降
通过 Performance 面板的记录,我们发现主要是这些原因导致:
// 示例:性能问题的代码模式
const ProductList = () => {
const [products, setProducts] = useState([])
const [filters, setFilters] = useState({})
// 问题1:每次渲染都会重新创建函数
const handleFilter = (newFilters) => {
setFilters(newFilters)
}
// 问题2:没有做数据缓存
const filteredProducts = products.filter(product => {
return Object.entries(filters).every(([key, value]) =>
product[key] === value
)
})
// 问题3:子组件没有做优化
return (
<div>
{filteredProducts.map(product => (
<ProductCard key={product.id} {...product} />
))}
</div>
)
}
优化方案实施
1. 组件优化
首先是最基础的组件优化。我们使用 React.memo 和 useMemo 来避免不必要的重渲染:
// 优化后的 ProductCard 组件
const ProductCard = React.memo(({ id, name, price, image }) => {
// 只有当props真正变化时才重新渲染
return (
<div className="product-card">
<img src={image} alt={name} loading="lazy" />
<h3>{name}</h3>
<p>{formatPrice(price)}</p>
</div>
)
}, (prevProps, nextProps) => {
// 自定义比较函数,只比较关键属性
return (
prevProps.id === nextProps.id &&
prevProps.price === nextProps.price
)
})
// 优化列表渲染
const ProductList = () => {
const [products, setProducts] = useState([])
const [filters, setFilters] = useState({})
// 缓存过滤函数
const handleFilter = useCallback((newFilters) => {
setFilters(newFilters)
}, [])
// 缓存过滤结果
const filteredProducts = useMemo(() => {
return products.filter(product => {
return Object.entries(filters).every(([key, value]) =>
product[key] === value
)
})
}, [products, filters])
return (
<div>
<FilterPanel onFilter={handleFilter} />
<VirtualizedList
items={filteredProducts}
renderItem={(product) => (
<ProductCard key={product.id} {...product} />
)}
/>
</div>
)
}
2. 数据处理优化
对于数据处理,我们采用了几个关键的优化策略:
// 1. 使用规范化的数据结构
interface NormalizedState {
products: {
byId: Record<string, Product>;
allIds: string[];
};
categories: {
byId: Record<string, Category>;
allIds: string[];
};
}
// 2. 实现高效的数据查询
const useProductsQuery = (filters: Filters) => {
const queryClient = useQueryClient()
return useQuery({
queryKey: ['products', filters],
queryFn: () => fetchProducts(filters),
// 实现数据预加载
placeholderData: () => {
// 使用已有数据作为占位
return queryClient.getQueryData(['products'])
},
// 智能缓存策略
staleTime: 5 * 60 * 1000, // 5分钟
cacheTime: 30 * 60 * 1000 // 30分钟
})
}
// 3. 优化状态更新
const useProductsStore = create((set) => ({
products: [],
setProducts: (newProducts) => {
set((state) => ({
products: produce(state.products, (draft) => {
// 使用 Immer 进行高效的不可变更新
draft.push(...newProducts)
})
}))
}
}))
3. 渲染优化
为了解决长列表渲染的问题,我们实现了虚拟滚动:
const VirtualizedList = ({ items, renderItem, itemHeight = 200 }) => {
const containerRef = useRef<HTMLDivElement>(null)
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 10 })
const onScroll = useCallback(() => {
if (!containerRef.current) return
const { scrollTop, clientHeight } = containerRef.current
const start = Math.floor(scrollTop / itemHeight)
const end = Math.min(
start + Math.ceil(clientHeight / itemHeight),
items.length
)
setVisibleRange({ start, end })
}, [itemHeight, items.length])
// 只渲染可见区域的项目
const visibleItems = useMemo(() => {
return items.slice(visibleRange.start, visibleRange.end)
}, [items, visibleRange])
return (
<div
ref={containerRef}
style={{ height: '100vh', overflow: 'auto' }}
onScroll={onScroll}
>
<div style={{ height: items.length * itemHeight }}>
<div style={{
transform: `translateY(${visibleRange.start * itemHeight}px)`
}}>
{visibleItems.map(renderItem)}
</div>
</div>
</div>
)
}
4. 网络优化
最后,我们还对网络请求进行了优化:
// 1. 实现请求去重和缓存
const requestCache = new Map()
const fetchWithCache = async (url: string, options = {}) => {
const cacheKey = `${url}-${JSON.stringify(options)}`
if (requestCache.has(cacheKey)) {
return requestCache.get(cacheKey)
}
const promise = fetch(url, options).then(res => res.json())
requestCache.set(cacheKey, promise)
try {
const result = await promise
return result
} finally {
// 5分钟后清除缓存
setTimeout(() => {
requestCache.delete(cacheKey)
}, 5 * 60 * 1000)
}
}
// 2. 实现数据预加载
const prefetchNextPage = (currentPage: number) => {
const nextPage = currentPage + 1
// 预加载下一页数据
fetchWithCache(`/api/products?page=${nextPage}`, {
priority: 'low' // 使用低优先级请求
})
}
优化效果
经过这一系列优化,我们取得了显著的效果:
- 首屏加载时间从3秒减少到1.2秒
- 列表滚动帧率稳定在60fps
- 搜索响应时间从500ms减少到100ms
- 内存占用减少40%
最让我欣慰的是收到用户的反馈:"现在浏览商品太流畅了!"这让之前的努力都值得了。
经验总结
React性能优化不是一蹴而就的,需要从多个层面系统地思考和实施:
- 组件层面:合理使用 memo、useMemo、useCallback
- 数据层面:规范化数据结构,实现高效的状态管理
- 渲染层面:采用虚拟滚动,延迟加载
- 网络层面:请求优化,数据预加载
写在最后
性能优化是一个持续的过程,不是一次性的工作。通过这次优化,我不仅解决了具体的性能问题,更重要的是建立了一套可持续的性能优化方法论。
如果你也在做React应用的性能优化,欢迎在评论区分享你的经验,让我们一起进步!
如果觉得这篇文章对你有帮助,别忘了点个赞 👍