vue3解决大数据加载页面卡顿问题

核心是利用 vxe-table 内置的大数据优化特性(虚拟滚动、懒加载等)+ 前端渲染优化

官网地址:https://vxetable.cn/#/demo/list

安装

javascript 复制代码
npm install vxe-table@next

核心优化:开启「虚拟滚动」(解决大数据渲染卡顿)

vxe-table 的 虚拟滚动 是解决大数据量(万级以上)卡顿的关键:只渲染「可视区域内的行 / 列」,而非全部数据,大幅减少 DOM 节点数量。

基础虚拟滚动(行虚拟)

适用于行数多、列数少的场景

javascript 复制代码
<template>
  <!-- 开启行虚拟滚动:设置 scroll-y 的 enabled + 固定高度 -->
  <vxe-table
    ref="xTableRef"
    v-model:data="tableData"
    :columns="tableColumns"
    :scroll-y="{
      enabled: true, // 开启行虚拟滚动
      height: 600,   // 固定表格高度(必须,虚拟滚动依赖固定高度)
      // 可选:缓冲区大小(可视区域外预渲染的行数,默认5)
      reserve: 5
    }"
    border
  />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { VxeTable, VxeTableProps } from 'vxe-table';
import 'vxe-table/lib/style.css';

// 模拟10万条大数据(实际从接口获取)
const tableData = ref<Array<{ id: number; name: string; age: number }>>([]);
// 列配置(TS类型提示)
const tableColumns = ref<VxeTableProps.Columns>([
  { field: 'id', title: 'ID', width: 100 },
  { field: 'name', title: '名称', width: 200 },
  { field: 'age', title: '年龄', width: 100 }
]);

// 初始化10万条数据
onMounted(() => {
  const mockData = Array.from({ length: 100000 }, (_, i) => ({
    id: i + 1,
    name: `用户${i + 1}`,
    age: Math.floor(Math.random() * 50) + 18
  }));
  tableData.value = mockData;
});
</script>

列虚拟滚动(行列双虚拟)

适用于行列都很多(比如列数超过 50)的场景,同时开启「行虚拟 + 列虚拟」:

javascript 复制代码
<template>
  <vxe-table
    v-model:data="tableData"
    :columns="tableColumns"
    <!-- 行虚拟 -->
    :scroll-y="{ enabled: true, height: 600 }"
    <!-- 列虚拟:设置 scroll-x 的 enabled + 固定宽度 -->
    :scroll-x="{
      enabled: true, // 开启列虚拟滚动
      width: '100%', // 表格宽度(必须)
      reserve: 3     // 列缓冲区大小
    }"
    border
  />
</template>

<script setup lang="ts">
// 模拟多列(比如100列)
const tableColumns = ref<VxeTableProps.Columns>([
  { field: 'id', title: 'ID', width: 100 },
  // 动态生成100列
  ...Array.from({ length: 100 }, (_, i) => ({
    field: `col${i}`,
    title: `列${i + 1}`,
    width: 150
  }))
]);
</script>

进阶优化:数据懒加载(解决一次性加载大数据卡顿)

如果数据量超过 10 万条,即使虚拟滚动,一次性加载全量数据也会占用大量内存,建议结合 滚动懒加载(滚动到底部时加载下一批数据)。

实现滚动懒加载(结合虚拟滚动)

javascript 复制代码
<template>
  <vxe-table
    ref="xTableRef"
    v-model:data="tableData"
    :columns="tableColumns"
    :scroll-y="{ enabled: true, height: 600 }"
    @scroll="handleTableScroll"
    border
  />
  <!-- 加载中提示 -->
  <div v-if="isLoading" class="loading">加载中...</div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';
import { VxeTable, VxeTableInstance } from 'vxe-table';

const xTableRef = ref<VxeTableInstance | null>(null);
const tableData = ref<Array<any>>([]);
const tableColumns = ref<VxeTableProps.Columns>([/* 列配置 */]);
const isLoading = ref(false);
let currentPage = 1;
const PAGE_SIZE = 2000; // 每次加载2000条(根据性能调整)

// 初始化加载第一页
onMounted(() => loadMoreData());

// 滚动到底部时加载下一页
const handleTableScroll = () => {
  const tableEl = xTableRef.value?.$el.querySelector('.vxe-table--body-wrapper');
  if (!tableEl || isLoading.value) return;
  const { scrollTop, scrollHeight, clientHeight } = tableEl;
  // 滚动到底部(留100px缓冲区)
  if (scrollTop + clientHeight >= scrollHeight - 100) {
    loadMoreData();
  }
};

// 加载更多数据
const loadMoreData = async () => {
  isLoading.value = true;
  try {
    // 模拟接口请求(实际替换为你的接口)
    const res = await mockApiRequest(currentPage, PAGE_SIZE);
    // 追加数据(虚拟滚动下直接push即可)
    tableData.value = [...tableData.value, ...res.data];
    currentPage++;
  } finally {
    isLoading.value = false;
  }
};

// 模拟接口请求
const mockApiRequest = (page: number, size: number) => {
  return new Promise<{ data: Array<any> }>((resolve) => {
    setTimeout(() => {
      const data = Array.from({ length: size }, (_, i) => ({
        id: (page - 1) * size + i + 1,
        name: `用户${(page - 1) * size + i + 1}`,
        age: Math.floor(Math.random() * 50) + 18
      }));
      resolve({ data });
    }, 300);
  });
};
</script>

额外性能优化点

优化列配置(减少不必要的渲染)

  • 缓存列配置:用 computed 缓存列配置,避免每次渲染重新生成:
javascript 复制代码
import { computed } from 'vue';
const tableColumns = computed<VxeTableProps.Columns>(() => [
  { field: 'id', title: 'ID', width: 100 },
  // ...其他列
]);
  • 避免复杂自定义渲染:减少 renderCell/formatter 中的复杂计算,提前处理数据:
javascript 复制代码
// 不好:渲染时计算
{
  field: 'age',
  title: '年龄',
  formatter: ({ row }) => `年龄:${row.age}岁` // 简单计算可以,但复杂逻辑提前处理
}

// 好:提前处理数据
tableData.value = mockData.map(item => ({
  ...item,
  ageText: `年龄:${item.age}岁` // 提前计算好
}));
{ field: 'ageText', title: '年龄' }

关闭不必要的表格功能

关闭 vxe-table 中不需要的交互 / 样式功能,减少 DOM 操作和事件监听:

javascript 复制代码
<vxe-table
  :show-header-overflow="false" // 关闭表头溢出提示
  :show-body-overflow="false"   // 关闭单元格溢出提示
  :highlight-hover-row="false"  // 关闭行hover高亮(减少DOM类名切换)
  :highlight-current-row="false"// 关闭当前行高亮
  :border="false"               // 不需要边框时关闭
  :loading="false"              // 不需要loading时关闭
/>

优化数据更新方式

  • 避免直接修改 tableData,使用 vxe-table 提供的 loadData 方法(内部做了性能优化):
javascript 复制代码
xTableRef.value?.loadData(newData); // 比直接赋值 tableData.value = newData 更高效
  • 批量更新数据:如果需要修改多条数据,用 setData 批量更新,减少重渲染次数:
javascript 复制代码
xTableRef.value?.setData((row) => {
  if (row.id === 100) {
    return { ...row, name: '新名称' };
  }
  return row;
});

事件防抖 / 节流

对表格的 sort-change、filter-change 等频繁触发的事件做防抖 / 节流:

javascript 复制代码
import { debounce } from 'lodash-es';

// 排序事件防抖(500ms触发一次)
const handleSortChange = debounce((params) => {
  // 处理排序逻辑
}, 500);

VxeTableList组件

javascript 复制代码
<!--
 * @Description: 
 * @Author: qianlishi
 * @Date: 2025-12-05 10:17:53
 * @LastEditors: Do not edit
 * @LastEditTime: 2025-12-05 17:25:02
-->
<template>
  <div class="table-container" ref="containerRef">
    <VxeTable
      border
      ref="tableRef"
      :data="tableData"
      :height="tableHeight"
      :column-config="{resizable: true}"
      :row-config="{isHover: true}"
      :virtual-y-config="{enabled: true, gt: 50}"
      @checkbox-all="handleSelectionChange"
      @checkbox-change="handleSelectionChange"
      :render-format="{ autoMerge: false }"
    >
      <vxe-column type="checkbox" width="50" fixed="left" />
      <vxe-column type="seq" width="70" />
      <vxe-column 
        v-for="item in tableColumns" 
        sortable
        :key="item.dataKey" 
        :field="item.dataKey"
        :title="item.title" 
        :min-width="item.width"
        :filters="item.fileList"
      />
    </VxeTable>
  </div>

</template>
<script setup lang="ts">
const props = defineProps({
  tableData: {
    type: Array,
    default: () => [],
  },
  tableColumns: {
    type: Array,
    default: () => [],
  },
})

const containerRef = ref<HTMLDivElement | null>(null)
const tableHeight = ref(0)

onMounted(async () => {
  await nextTick()
  setTableHeight()
})
const setTableHeight = () => {
  if (!containerRef.value) return
  const containerHeight = containerRef.value.clientHeight
  tableHeight.value = containerHeight
}

window.addEventListener('resize', () => {
  setTableHeight()
})

onMounted(() => {
  if (!containerRef.value) return
  const resizeObserver = new ResizeObserver(() => {
    setTableHeight()
  })
  resizeObserver.observe(containerRef.value)
})
// 复选框选中
const handleSelectionChange = (params: any) => { 
  emits('selection-change', params.records)
}

const emits = defineEmits(['selection-change']) 
</script>
<style scoped lang="scss">
.table-container {
  height: calc(100vh - 210px);
  :deep(.vxe-table--column) {
    font-weight: normal;
    color: #636369;
  }
  :deep(.col--filter) {
    .vxe-cell--filter {
      float: right;
      vertical-align: middle;
      margin-top: 3px;
    }
    .vxe-filter--btn::before {
      content: '' !important;
      display: inline-block;
      width: 16px;
      height: 16px;
      background: url('@/assets/svgs/doc/filter.svg') no-repeat center center;
      background-size: 100% 100%;
      vertical-align: middle;
    }
    .vxe-cell--title {
      color: #636369;
    }
  }
  :deep(.is--filter-active) {
    .vxe-cell--filter {
      float: right;
      vertical-align: middle;
      margin-top: 3px;
    }
    .vxe-filter--btn::before {
      content: '' !important;
      display: inline-block;
      width: 16px; 
      height: 16px;
      background: url('@/assets/svgs/doc/filter-checked.svg') no-repeat center center;
      background-size: 100% 100%; // 适配图片尺寸
      vertical-align: middle;
    }
    .vxe-cell--title {
      color: #005aae;
    }
  }
}
</style>
相关推荐
福客AI智能客服4 小时前
从被动响应到主动赋能:家具行业客服机器人的革新路径
大数据·人工智能
小五传输5 小时前
隔离网闸的作用是什么?新型网闸如何构筑“数字护城河”?
大数据·运维·安全
jkyy20146 小时前
AI健康医疗开放平台:企业健康业务的“新基建”
大数据·人工智能·科技·健康医疗
蚁巡信息巡查系统6 小时前
政府网站与政务新媒体检查指标抽查通报如何面对
大数据·内容运营
视界先声7 小时前
2025年GEO自动化闭环构建实践:监测工具选型与多平台反馈机制工程分享
大数据·人工智能·自动化
百***24377 小时前
GPT5.1 vs Claude-Opus-4.5 全维度对比及快速接入实战
大数据·人工智能·gpt
AI营销前沿7 小时前
私域AI首倡者韩剑,原圈科技领航AI营销
大数据·人工智能
Percent_bigdata8 小时前
数据治理平台选型解析:AI大模型与智能体如何重塑企业数字基座
大数据·人工智能
hg01188 小时前
广西对外农业投资规模稳增 民营企业成主力军
大数据