当后端一次性返回十万条数据时,前端需要采用多种性能优化策略来避免页面卡顿。以下是主要的优化方案:

  1. 分页加载 - 将数据分批次加载显示
  2. 虚拟滚动 - 只渲染可视区域内的数据
  3. 数据懒加载 - 按需加载数据
  4. Web Workers - 在后台线程处理数据
  5. 时间切片 - 分散渲染任务避免阻塞主线程

下面是具体的实现代码:

<template><div class="large-data-view"><h2>大数据量处理示例</h2><!-- 性能监控 --><div class="performance-info"><span>总数据量: {{ totalDataCount }}</span><span>当前显示: {{ startIndex + 1 }} - {{ Math.min(startIndex + pageSize, totalDataCount) }}</span><span>渲染耗时: {{ renderTime }}ms</span></div><!-- 分页控件 --><div class="pagination-controls"><button @click="prevPage" :disabled="currentPage <= 1">上一页</button><span>第 {{ currentPage }} 页,共 {{ totalPages }} 页</span><button @click="nextPage" :disabled="currentPage >= totalPages">下一页</button><select v-model="pageSize" @change="onPageSizeChange"><option value="50">每页50条</option><option value="100">每页100条</option><option value="200">每页200条</option><option value="500">每页500条</option></select></div><!-- 虚拟滚动列表 --><div class="virtual-scroll-container" ref="scrollContainer" @scroll="onScroll"><div class="scroll-placeholder" :style="{ height: totalHeight + 'px' }"></div><div class="visible-items" :style="{ transform: `translateY(${offsetY}px)` }"><div class="data-item" v-for="item in visibleItems" :key="item.id":style="{ height: itemHeight + 'px' }"><span class="item-index">{{ item.id }}</span><span class="item-name">{{ item.name }}</span><span class="item-value">{{ item.value }}</span><span class="item-time">{{ formatTime(item.timestamp) }}</span></div></div></div><!-- 加载指示器 --><div v-if="loading" class="loading-indicator">数据加载中...</div></div>
</template><script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue';// 响应式数据
const allData = ref<any[]>([]);
const startIndex = ref(0);
const pageSize = ref(100);
const itemHeight = 50; // 每项高度
const loading = ref(false);
const renderTime = ref(0);
const scrollContainer = ref<HTMLElement | null>(null);// 分页相关数据
const currentPage = computed(() => Math.floor(startIndex.value / pageSize.value) + 1);
const totalPages = computed(() => Math.ceil(allData.value.length / pageSize.value));
const totalDataCount = computed(() => allData.value.length);// 虚拟滚动相关数据
const visibleCount = ref(15); // 可视区域项目数
const scrollTop = ref(0);
const offsetY = ref(0);
const totalHeight = computed(() => allData.value.length * itemHeight);// 可视区域的数据
const visibleItems = computed(() => {const start = Math.max(0, Math.floor(scrollTop.value / itemHeight));const end = Math.min(allData.value.length, start + visibleCount.value);return allData.value.slice(start, end);
});// 方法
const formatTime = (timestamp: number) => {const date = new Date(timestamp);return date.toLocaleTimeString();
};// 分页方法
const prevPage = () => {if (currentPage.value > 1) {startIndex.value = (currentPage.value - 2) * pageSize.value;scrollToTop();}
};const nextPage = () => {if (currentPage.value < totalPages.value) {startIndex.value = currentPage.value * pageSize.value;scrollToTop();}
};const onPageSizeChange = () => {startIndex.value = 0;scrollToTop();
};const scrollToTop = () => {if (scrollContainer.value) {scrollContainer.value.scrollTop = 0;}
};// 虚拟滚动处理
const onScroll = () => {if (scrollContainer.value) {scrollTop.value = scrollContainer.value.scrollTop;offsetY.value = Math.floor(scrollTop.value / itemHeight) * itemHeight;}
};// 模拟加载大数据
const loadData = async () => {loading.value = true;const startTime = performance.now();try {// 模拟从后端获取10万条数据// 实际项目中这里应该是API调用const data = generateLargeData(100000);allData.value = data;// 使用时间切片技术处理数据渲染await nextTick();const endTime = performance.now();renderTime.value = Math.round(endTime - startTime);} finally {loading.value = false;}
};// 生成模拟数据
const generateLargeData = (count: number) => {const data = [];for (let i = 0; i < count; i++) {data.push({id: i + 1,name: `数据项 ${i + 1}`,value: Math.random() * 1000,timestamp: Date.now() - Math.floor(Math.random() * 10000000)});}return data;
};// 初始化
onMounted(() => {loadData();// 计算可视区域能显示的项目数if (scrollContainer.value) {visibleCount.value = Math.ceil(scrollContainer.value.clientHeight / itemHeight) + 5;}
});
</script><style scoped>
.large-data-view {height: 100%;display: flex;flex-direction: column;padding: 20px;
}.performance-info {display: flex;gap: 20px;margin-bottom: 20px;padding: 10px;background-color: #f5f5f5;border-radius: 4px;
}.pagination-controls {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;padding: 10px;background-color: #f9f9f9;border-radius: 4px;
}.pagination-controls button {padding: 6px 12px;background-color: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer;
}.pagination-controls button:disabled {background-color: #ccc;cursor: not-allowed;
}.pagination-controls select {padding: 6px;border: 1px solid #ddd;border-radius: 4px;
}.virtual-scroll-container {flex: 1;border: 1px solid #ddd;border-radius: 4px;overflow: auto;position: relative;
}.scroll-placeholder {position: absolute;left: 0;top: 0;right: 0;z-index: -1;
}.visible-items {position: absolute;left: 0;right: 0;top: 0;z-index: 1;
}.data-item {display: flex;align-items: center;padding: 0 15px;border-bottom: 1px solid #eee;
}.item-index {width: 80px;font-weight: bold;
}.item-name {flex: 1;
}.item-value {width: 120px;text-align: right;
}.item-time {width: 100px;text-align: right;color: #999;font-size: 0.9em;
}.loading-indicator {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);padding: 20px;background-color: rgba(0, 0, 0, 0.8);color: white;border-radius: 4px;z-index: 1000;
}
</style>
/*** 大数据处理工具类* 使用Web Workers和时间切片技术处理大量数据*/// 时间切片处理大数据
export class DataProcessor {private chunkSize: number;constructor(chunkSize: number = 1000) {this.chunkSize = chunkSize;}/*** 分块处理大数据数组* @param data 大数据数组* @param processor 处理函数* @returns Promise*/async processInChunks<T, R>(data: T[], processor: (item: T) => R): Promise<R[]> {const results: R[] = [];const totalChunks = Math.ceil(data.length / this.chunkSize);for (let i = 0; i < totalChunks; i++) {const start = i * this.chunkSize;const end = Math.min(start + this.chunkSize, data.length);const chunk = data.slice(start, end);// 处理当前块const chunkResults = chunk.map(processor);results.push(...chunkResults);// 让出控制权,避免阻塞UIif (i < totalChunks - 1) {await this.yieldToMain();}}return results;}/*** 让出控制权给主线程*/private yieldToMain(): Promise<void> {return new Promise(resolve => {setTimeout(resolve, 0);});}/*** 使用Web Worker处理数据* @param data 数据* @param workerFunction 处理函数字符串* @returns Promise*/processWithWorker<T, R>(data: T[], workerFunction: string): Promise<R[]> {return new Promise((resolve, reject) => {// 创建Web Workerconst workerCode = `self.onmessage = function(e) {const { data, processor } = e.data;const func = new Function('return ' + processor)();const results = data.map(func);self.postMessage(results);};`;const blob = new Blob([workerCode], { type: 'application/javascript' });const worker = new Worker(URL.createObjectURL(blob));worker.onmessage = function(e) {resolve(e.data);worker.terminate();};worker.onerror = function(error) {reject(error);worker.terminate();};worker.postMessage({data,processor: workerFunction});});}
}// 创建数据处理器实例
export const dataProcessor = new DataProcessor(1000);
<template><div class="infinite-scroll-list" ref="container"><div class="list-container"><div class="list-item" v-for="item in displayItems" :key="item.id"><slot :item="item"></slot></div><div v-if="loading" class="loading-more">加载中...</div><div v-if="noMore" class="no-more">没有更多数据了</div></div></div>
</template><script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';const props = defineProps<{items: any[];pageSize?: number;threshold?: number; // 距离底部多少像素时触发加载
}>();const emit = defineEmits<{(e: 'loadMore'): void;
}>();// 默认值
const pageSize = props.pageSize || 50;
const threshold = props.threshold || 100;// 响应式数据
const container = ref<HTMLElement | null>(null);
const displayedCount = ref(pageSize);
const loading = ref(false);
const noMore = ref(false);// 计算属性
const displayItems = computed(() => {return props.items.slice(0, displayedCount.value);
});// 方法
const handleScroll = () => {if (!container.value) return;const { scrollTop, scrollHeight, clientHeight } = container.value;const distanceToBottom = scrollHeight - scrollTop - clientHeight;// 当距离底部小于阈值且还有数据时触发加载if (distanceToBottom < threshold && !loading && !noMore.value) {loadMore();}
};const loadMore = () => {if (displayedCount.value >= props.items.length) {noMore.value = true;return;}loading.value = true;// 模拟异步加载setTimeout(() => {displayedCount.value = Math.min(displayedCount.value + pageSize,props.items.length);loading.value = false;if (displayedCount.value >= props.items.length) {noMore.value = true;}}, 300);
};// 暴露方法给父组件
defineExpose({reset() {displayedCount.value = pageSize;noMore.value = false;},setLoading(status: boolean) {loading.value = status;}
});// 生命周期
onMounted(() => {if (container.value) {container.value.addEventListener('scroll', handleScroll);}
});onBeforeUnmount(() => {if (container.value) {container.value.removeEventListener('scroll', handleScroll);}
});
</script><style scoped>
.infinite-scroll-list {height: 100%;overflow-y: auto;
}.list-item {padding: 10px;border-bottom: 1px solid #eee;
}.loading-more,
.no-more {padding: 15px;text-align: center;color: #999;
}
</style>

这些优化方案可以有效解决前端处理大量数据时的卡顿问题:

  1. 分页加载:将10万条数据分页显示,每次只渲染少量数据
  2. 虚拟滚动:只渲染可视区域内的数据项,大幅减少DOM节点数量
  3. 时间切片:将大数据处理任务分解成小块,避免长时间阻塞主线程
  4. 按需渲染:根据用户滚动位置动态加载和卸载数据
  5. 性能监控:实时显示渲染性能指标,便于调优

通过这些技术的组合使用,即使面对10万条数据,页面也能保持流畅的用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97895.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97895.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/97895.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于-轻量级文档搜索系统的测试报告

文章目录一、项目背景二、项目功能三、测试计划&#xff08;一&#xff09;测试用例设计&#xff08;二&#xff09;测试用例实现1.功能测试2.界面测试3.兼容性测试4.易用性测试5.安全性测试一、项目背景 1.基于轻量级文档检索系统采用C技术栈来实现&#xff0c;同时使用了本地…

编辑器vim(Linux)

Linux下开发工具是独立的写代码——编辑器 vim编译代码——gcc/g调试——gdb、cgdb构建工具——makefile、make、cmakevim只用来写代码注意&#xff1a;直接用vim打开一个不存在的文件并保存退出&#xff0c;就会自动生成该文件vim有多种模式命令模式&#xff08;Normal Mode&a…

GitLab,2025最新如何配置中的SSH key步骤

电脑右键先检查&#xff0c;是否有公钥 git cat ~/.ssh/id_rsa.pub下面是有&#xff0c;不用生成公钥&#xff0c;没有就要生成生成本地电脑公钥, 建议用第二种 //第一种ssh-keygen -t rsa//第二种------- 1.打开git bash,输入&#xff1a;ssh-keygen -t rsa -C “你的邮箱”ss…

华为HCIE证书多久续一次费?费用多少?

根据华为官方政策&#xff0c;华为认证HCIE的有效期为3年&#xff0c;有效期自证书正式发放之日起计算&#xff0c;考生可通过华为人才在线官网登录个人账号&#xff0c;在“我的证书”栏目中查询具体有效期起止时间。一、HCIE证书到期后的续证方式 1.重考对应HCIE的认证考试&a…

提升文本到图像强化学习稳定性:Pref - GRPO算法如何革新图像生成?

提升文本到图像强化学习稳定性&#xff1a;Pref - GRPO算法如何革新图像生成&#xff1f; 在文本到图像生成领域&#xff0c;强化学习正重塑着模型与人类偏好的对齐方式。本文聚焦于一种创新的基于成对偏好奖励的GRPO方法&#xff08;Pref - GRPO&#xff09;&#xff0c;它通…

Linux UDisks守护进程曝本地提权漏洞CVE-2025-8067,PoC已发布

漏洞概述安全研究人员在Linux环境中广泛使用的磁盘管理组件UDisks守护进程中&#xff0c;发现了一个严重漏洞&#xff08;编号CVE-2025-8067&#xff0c;CVSS评分8.5&#xff09;。该漏洞已报告给红帽产品安全团队&#xff0c;并在UDisks更新版本中得到修复。技术细节该漏洞存在…

uniapp 开发上架 iOS App全流程

操作文档网址&#xff1a;https://ask.dcloud.net.cn/article/152 操作学习视频地址&#xff1a;uniapp打包上线微信小程序、安卓、IOS流程_哔哩哔哩_bilibili 第一步&#xff1a;注册苹果 iOS 个人开发者账号 费用说明 ‌个人开发者账号‌&#xff1a;适用于独立开发者或小…

Sqlsugar补充自定义模板

DBFirst默认创建所有实体CreateClassFile()的第二个参数为生成实体类命名空间//.net6以下 db.DbFirst.IsCreateAttribute().CreateClassFile("c:\\Demo\\1", "Models"); //.net6以上 string加? db.DbFirst.IsCreateAttribute().StringNullable().CreateCl…

LeetCode 392.判断子序列

给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子序列&#x…

逻辑回归:从原理到实战的完整指南

在机器学习中&#xff0c;分类任务是最常见的应用场景之一。而逻辑回归&#xff08;Logistic Regression&#xff09;&#xff0c;尽管名字中有“回归”&#xff0c;实际上是一种非常强大且广泛应用的二分类模型。它简单、高效、可解释性强&#xff0c;是数据科学初学者入门分类…

鸿蒙搭配前端开发:应用端与WEB端交互

鸿蒙系统&#xff08;HarmonyOS&#xff09;是华为开发的一款面向全场景的分布式操作系统&#xff0c;其设计初衷是为了适应物联网时代的需求&#xff0c;旨在构建一个统一的操作系统&#xff0c;支持多种设备的无缝协同工作。其分布式开发的一些主要优势&#xff1a; 跨设备协…

配置sscms时被sql server处处刁难

今天要记下来的是一个小例子。接前面&#xff0c;当我终于完成sql server的安装时&#xff0c;才发现要填写sscms的两个空是有多么艰难。首先安装sql server2016出现了太多环境不兼容的问题&#xff0c;让我只好退而安装sql server2012。安装sql server2012时其实是可以避坑的&…

【Flink】DataStream API:源算子、数据类型

目录源算子&#xff08;Source&#xff09;从集合中读取数据从文件读取数据从Socket读取数据从Kafka读取数据从数据生成器读取数据Flink支持的数据类型Flink的类型系统Flink支持的数据类型类型提示&#xff08;Type Hints&#xff09;源算子&#xff08;Source&#xff09; Fli…

Linux 安装docker-compose安装方法(安装docker compose安装)

文章目录**方法一&#xff1a;通过 curl 下载二进制文件&#xff08;推荐&#xff09;**1. 安装前准备- **确保已安装 Docker**- **检查 Docker 是否安装成功**2. 下载并安装 Docker Compose- **下载最新版本的 Docker Compose 二进制文件**- **国内加速下载&#xff08;解决 G…

OCR 发票识别与验真接口:助力电子化发票新时代

自 2025 年 10 月 1 日起&#xff0c;纸质火车票彻底告别历史舞台&#xff0c;全面数字化的电子发票取而代之&#xff0c;这一变革标志着票务领域的重大革新&#xff0c;也让电子化发票处理的需求呈井喷式增长。在此背景下&#xff0c;OCR 发票识别和发票验真接口技术挺身而出&…

设计模式:抽象工厂模式(Abstract Factory Pattern)

文章目录一、概念二、实例分析三、完整示例一、概念 抽象工厂模式是一种创建型设计模式。 提供一个接口用于创建一系列相关或相互依赖的对象&#xff0c;而无需指定它们的具体类。 相比于工厂方法模式&#xff0c;抽象工厂模式不仅仅是创建单一产品&#xff0c;而是一族产品&am…

轻量级注意力模型HOTSPOT-YOLO:无人机光伏热异常检测新SOTA,mAP高达90.8%

【导读】 无人机光伏巡检如何更智能、更高效&#xff1f;HOTSPOT-YOLO模型给出了亮眼答案&#xff01;给AI装上“热成像鹰眼”&#xff0c;能精准锁定光伏板上的细微热斑缺陷。它不仅将检测精度&#xff08;mAP&#xff09;提升至90.8%&#xff0c;更在保持实时性的前提下大幅…

CHT共轭传热: 导热系数差异如何影响矩阵系数

文章目录 一、导热系数差异如何影响矩阵系数&#xff1f;二、如何处理系数差异以加速收敛&#xff1f;1. **变量重缩放&#xff08;Scaling of Variables&#xff09;**2. **使用物理型预条件子&#xff08;Physics-based Preconditioning&#xff09;**3. **区域分解法&#x…

Vue Vapor 事件机制深潜:从设计动机到源码解析

基于 vue3.6&#xff08;alpha 阶段&#xff09;及 Vapor 的最新进展撰写&#xff1b;Vapor 仍在演进中&#xff0c;部分实现可能继续优化。TL;DR&#xff08;速览&#xff09; 传统&#xff08;≤3.5&#xff09;&#xff1a;事件以元素为中心绑定&#xff1b;每个元素用 el._…

Day 01(01): Hadoop与大数据基石

目标&#xff1a;建立对大数据生态的整体认知&#xff0c;理解HDFS和MapReduce的核心思想。 8:00-9:30&#xff1a;【视频学习】在B站搜索“Hadoop入门”或“三小时入门大数据”&#xff0c;观看1-2个高播放量的简介视频&#xff0c;了解大数据面临的问题和Hadoop的解决方案。 …