You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue/Nuxt 大型数据集拉取渲染卡顿,长列表加载优化方案求助

核心问题定位

你遇到的卡顿、崩溃主要来自两个层面:

  1. 逻辑层:所有分类共用同一份条目存储变量、快速点击触发的请求竞态、重复请求导致多余的渲染开销
  2. 渲染层:单分类条目过多时一次性渲染全量DOM节点,浏览器重排重绘压力过载

具体优化方案

1. 修复现有逻辑缺陷

优化数据存储逻辑

不要所有分类共用全局的categoryItems变量,给每个分类独立分配条目存储字段,已经拉取过的分类不用重复发请求:

// 初始化分类数据时新增两个字段:存储自身条目的items、标记是否已经拉取过数据的loaded
this.categories = this.categories.map(cat => ({
  ...cat,
  items: [],
  loaded: false
}))

新增请求取消机制,避免竞态

用axios内置的CancelToken,切换分类时自动取消上一个未完成的请求,避免后返回的旧请求覆盖新请求的数据,也减少多余的响应处理:

// data 新增变量存储取消函数
data() {
  return {
    // ...其他原有变量
    cancelRequest: null
  }
},
methods: {
  getCategoryItems(index) {
    // 取消上一个未完成的请求
    if (this.cancelRequest) this.cancelRequest()
    const currentCat = this.categories[index]
    // 已经拉过数据的直接跳过请求
    if (currentCat.loaded) return

    this.isOpen = index
    this.loading()
    const catCode = currentCat.code

    this.$axios
      .get('/api/items', {
        params: { category: catCode },
        // 注入取消令牌
        cancelToken: new this.$axios.CancelToken(c => {
          this.cancelRequest = c
        })
      })
      .then(response => {
        currentCat.items = response.data
        currentCat.loaded = true
      })
      .catch(error => {
        // 忽略主动取消的请求报错
        if (!this.$axios.isCancel(error)) console.log(error)
      })
  }
}

优化渲染key

分类列表的key不要用索引index,改用分类的唯一code,避免列表diff时不必要的全量重渲染:

<b-collapse
  v-for="(category, index) of categories"
  :key="category.code"
  :open="isOpen == index"
  @open="getCategoryItems(index)"
  class="card"
  animation="none" <!-- 关闭动画,减少展开时的重绘压力 -->
>

条目列表渲染时,改用当前分类独立的items字段:

<div v-for="item in category.items" :key="item.id" class="category-item">

2. 长列表渲染优化(核心优化)

单分类条目超过200条时,一次性渲染全量DOM会直接导致浏览器卡顿,推荐用虚拟滚动方案,只渲染可视区域内的条目,不管总条目有多少,同时渲染的DOM节点始终保持在几十条的量级:
根据你使用的Vue版本引入对应版本的虚拟滚动组件即可,使用示例如下:

<!-- 给列表容器设置固定高度,开启滚动 -->
<div class="content" style="height: 500px; overflow-y: auto;">
  <virtual-scroller
    :items="category.items"
    :item-height="40" <!-- 按实际单条目的高度调整 -->
    class="scroller-content"
  >
    <template #default="{ item }">
      <div class="category-item">
        <b-icon icon="book-plus" size="is-small"> </b-icon>
        <a @click="selectedItem(item)" class="category-item-title" v-once>
          {{ item.description }}
        </a>
      </div>
    </template>
  </virtual-scroller>
  <b-loading :is-full-page="false" v-model="isLoading"></b-loading>
</div>

如果不想引入额外依赖,也可以给分类条目做滚动分页,滚动到列表底部再加载下一页数据,避免一次性拉取和渲染全量条目。

3. 细节优化

  • 条目内的静态内容可以加v-once指令,只渲染一次,减少后续diff开销
  • 如果不需要展开动画,可以直接去掉animation="slide"配置,减少展开时的重绘压力
  • 后端接口可以加gzip压缩,减少大列表返回的数据包体积,加快请求响应速度

内容的提问来源于stack exchange,提问作者Sam L.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 10:54:03