"这个列表页怎么这么卡?"产品经理皱着眉头看着我。作为一个接手海外电商项目的前端开发者,我深知性能问题的重要性。特别是在东南亚市场,很多用户使用的是中低端手机,网络条件也不太理想。
最近一个月,我带领团队对整个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} /><VirtualizedListitems={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) returnconst { scrollTop, clientHeight } = containerRef.currentconst 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 promisereturn 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应用的性能优化,欢迎在评论区分享你的经验,让我们一起进步!
如果觉得这篇文章对你有帮助,别忘了点个赞 👍