避免渲染大量数据造成页面卡顿——虚拟滚动

后端一次性返回太多数据需要前端全部渲染造成页面卡顿或者浏览器崩溃怎么办?

常见处理方式是和后端沟通数据分页,真正减少数据量,但若不具备此条件需要仅在前端处理或前端需要展示全部数量的情况,可以使用虚拟滚动的方式。

【需求】Vue项目,后端传过来的数据对象数组items, 在前端以表格展示,并且部分列可以修改,点击提交按钮将修改后的数据全部回传。

【实现】引入vue-virtual-scroll-list库,一个适配Vue2项目的轻量级虚拟滚动库

scss 复制代码
npm install vue-virtual-scroll-list --save

vue-virtual-scroll-list 提供了一个名为 virtual-list 的组件,你需要将它注册到你的 Vue 组件中,并传入三个核心属性:

  • data-key :数据项的唯一标识字段(如 id)。
  • data-sources:数据源数组(即你的 9000 条数据)。
  • data-component:用于渲染每个列表项的子组件。

同时,需要给 virtual-list 设置一个固定高度(或最大高度),并开启滚动。

完整示例如下:

父组件

ruby 复制代码
<div class="list-container">
    <virtual-list
        style="height: 500px; overflow-y: auto;"
        :data-key="'id'"
        :data-sources="items"
        :estimate-size="60"
        :keeps="30"
        :data-component="rowComponent"
        :extra-props="{ onUpdateCell: handleUpdateCell }"
    >
    </virtual-list>
</div>
kotlin 复制代码
handleUpdateCell(data: any){
  this.items[data.index][data.column] = data.value;
}

子组件

<template> 复制代码
>    <div class="tr">
        <div class="td" style="width: 100px;">{{ index }}</div>
        <div class="td" style="width: 200px;">{{ source.articleId }}</div>
        <div class="td" style="width: 400px;">{{ source.articleName }}</div>
        <div class="td" style="width: 100px;" @dblclick="startEdit('qty')">
            <input
                v-if="editingColumn === 'qty'"
                ref="input"
                v-model="editingValue"
                class="edit-input"
                @keyup.enter="saveEdit('qty',index)"
            />
            <span v-else>{{ source.qty }}</span>
        </div>

    </div>
</template>

<script>
export default {
  name: 'TableRow',
  props: {
    // virtual-list 会自动将数据项以 'source' 属性传递给子组件
    source: {
      type: Object,
      required: true
    },
    // 可选:索引值(如果需要在模板中显示索引)
    index: {
      type: Number
    },
    onUpdateCell: { type: Function, required: true }  // 接收父组件传递的方法
  },
  data() {
    return {
      editingColumn: null, // 编辑列
      editingValue: ''     // 临时编辑值
    };
  },
  methods: {
    // 双击进入编辑模式
    startEdit(column) {
      this.editingColumn = column;
      // 初始化临时值为当前字段值
      this.editingValue = this.source[column];
      // 自动聚焦输入框
      this.$nextTick(() => {
        if (this.$refs.input) {
          this.$refs.input.focus();
        }
      });
    },
    // 保存编辑
    saveEdit(column,index) {

      if (this.editingColumn !== column) return; // 防止误触发

      // 只有当值发生变化时才触发更新事件
      if (this.editingValue !== this.source[column]) {
        console.log('saveEdit',column,index,this.editingValue)
        // 直接调用父组件传递的方法,而不是 $emit
        this.onUpdateCell({
          index: index,
          column,
          value: this.editingValue
        });
      }
      // 退出编辑模式
      this.editingColumn = null;
    }
  }
};
</script>

<style scoped>
.tr {
  display: flex;           /* 使用 flex 保证每列宽度按设定分配 */
  width: 100%;
  border-bottom: 1px solid #eee;
}
.td {
  padding: 8px;
  box-sizing: border-box;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.editable-text {
  cursor: pointer;
  display: block;
  width: 100%;
}
.edit-input {
  width: 100%;
  padding: 4px;
  border: 1px solid #1890ff;
  border-radius: 2px;
  outline: none;
}
</style>

【注】

子传父不能用$emit,Vue 的自定义事件不会自动跨组件冒泡 ,因此父组件在 virtual-list 标签上监听 @update-cell 是无效的------virtual-list 本身并没有 $emit 这个事件,它只是作为容器渲染了子组件。

相关推荐
林焱_RPAAI17 小时前
影刀RPA技术深度:CSS选择器高级实战指南——伪类属性选择器与性能对比完全解析
vue.js·react.js
陳陈陳18 小时前
🚀 前端流式输出革命:SSE + BFF 架构从零到一实战指南
vue.js·架构·node.js
黄泉路醉s1 天前
在Vant+Vue+TypeScript的H移动前端使用UnoCSS
前端·vue.js·typescript
我要两颗404西柚1 天前
Stage three:VUE工程化与实战工具
前端·javascript·vue.js
看昭奚恤哭1 天前
基于 RuoYi-Vue-Pro 定制了一个后台管理系统 magic-admin , 开源出来!
前端·vue.js·开源
妙码生花2 天前
从 PHP 到 AI + Golang,程序员自救转型手记(四十五):前端远程下拉输入组件
前端·javascript·vue.js
Listen·Rain2 天前
AGENTS.md — Vue 3 Frontend Development
前端·javascript·vue.js
南风知我意啊2 天前
Vue3图片缩放拖拽组件全攻略
前端·javascript·vue.js
万亿少女的梦1682 天前
基于Spring Boot、Vue.js与MySQL的流浪猫狗救助系统设计与实现
vue.js·spring boot·mysql·javaweb·权限管理
早睡早起身体好1232 天前
Vue 3 路由与懒加载入门:从零完成一个迷你学习中心
javascript·vue.js·学习