前端性能优化实战总结与关键技巧解析

国曼 框架 阅读 1,595
赞 7 收藏
二维码
手机扫码查看
反馈

性能优化又翻车了,这次是列表渲染太慢

最近在做一个商城项目,首页的商品列表数据量有点大,大概三四百条的样子。一开始觉得这数据量不大啊,结果真机测试的时候发现页面卡得不行,尤其是低端安卓机上,滚动起来跟幻灯片似的。

前端性能优化实战总结与关键技巧解析

这里我踩了个坑,一开始以为是接口请求的问题,折腾了半天去优化后端接口,甚至还加了缓存。后来用Chrome DevTools的Performance面板一测,发现问题出在前端渲染上。

三种方案对比,我选了最简单的

刚开始我试了几个方法,比如直接用CSS的will-change属性来优化滚动:

.list-item {
  will-change: transform;
}

这个方法亲测有效,但效果不够明显。尤其在低端机上,还是会有明显的卡顿。

接着我又试了虚拟列表,就是只渲染可视区域的内容。这个方案确实能解决问题,但实现起来比较复杂,需要计算每个item的高度,还要监听滚动事件动态更新。对于一个赶工期的项目来说,实在没那么多时间折腾。

最后我选择了Intersection Observer API来做懒加载,简单粗暴又好用。虽然不是最优解,但对于当前项目来说够用了。

核心代码就这几行

下面直接上代码,这是我最终的实现方案:

// 商品列表组件
export default {
  data() {
    return {
      items: [], // 所有商品数据
      visibleItems: [] // 当前可见的商品数据
    }
  },
  mounted() {
    this.loadItems()
    this.initObserver()
  },
  methods: {
    async loadItems() {
      const res = await fetch('https://jztheme.com/api/products')
      this.items = await res.json()
      this.visibleItems = this.items.slice(0, 20) // 先加载前20条
    },
    initObserver() {
      const observer = new IntersectionObserver(entries => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const index = parseInt(entry.target.dataset.index)
            this.loadMore(index)
            observer.unobserve(entry.target)
          }
        })
      }, { threshold: 0.5 })

      this.$refs.sentinel && observer.observe(this.$refs.sentinel)
    },
    loadMore(index) {
      const newItems = this.items.slice(0, index + 30) // 每次多加载30条
      this.visibleItems = newItems
    }
  }
}
<template>
  <div>
    <div v-for="(item, index) in visibleItems" :key="item.id">
      <ProductItem :data="item" />
    </div>
    <div ref="sentinel" style="height: 1px;"></div>
  </div>
</template>

这里有几个点需要注意:

  • sentinel元素:这是用来标记观察位置的,放在列表底部
  • threshold参数:设置为0.5表示当元素一半进入视口时就开始加载
  • 分批加载:每次加载30条,这个数量可以根据实际情况调整

踩坑提醒:这三点一定注意

在实现过程中我遇到了几个坑,分享给大家避雷:

  1. 不要在Intersection Observer回调里直接修改DOM,最好通过Vue的数据驱动方式更新
  2. 记得在组件销毁时调用observer.disconnect(),否则会造成内存泄漏
  3. 如果列表项高度不固定,需要使用rootMargin参数预留更多观察空间

还有一个小问题没解决,当快速滚动时偶尔会出现白屏,不过持续时间很短,基本不影响用户体验,暂时就这样了。

为啥不用虚拟列表?说说我的想法

其实虚拟列表确实是最佳方案,像react-window这些库都很成熟。但我觉得对这个项目来说有点杀鸡用牛刀了。

主要原因有几点:

  • 项目工期紧,老板天天催着上线
  • 数据量虽然大,但还没到必须用虚拟列表的程度
  • Intersection Observer兼容性已经很不错了,主流浏览器都支持

当然,如果你的项目数据量特别大,或者对性能要求特别高,建议还是老老实实用虚拟列表。

以上是我踩坑后的总结,希望对你有帮助

这次性能优化的经历让我明白,有时候最简单的方案反而最适合当前的场景。虽然最终方案不是理论上最优的,但综合考虑开发成本和实际效果,我觉得这个选择是对的。

如果你有更好的实现方式,或者对这个方案有什么改进建议,欢迎在评论区交流。后续我还会继续分享一些实战中的性能优化经验,敬请期待。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论