"这个列表页怎么这么卡?"产品司理皱着眉头看着我。作为一个接手外洋电商项目的前端开辟者,我深知性能问题的紧张性。特殊是在东南亚市场,许多用户利用的是中低端手机,网络条件也不太理想。
最近一个月,我带领团队对整个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应用的性能优化,欢迎在评论区分享你的经验,让我们一起进步!
如果以为这篇文章对你有帮助,别忘了点个赞 |