最近有个朋友问我:"后端一次性返回十万条数据,页面直接卡死了,怎么办?" 这确实是个很常见的问题。今天我就来分享几种实用的解决方案。
我们来算一笔账。十万条数据,就算每条数据只有几个字段,内存占用至少20-50MB。如果把这些数据都渲染成dom元素,轻轻松松就是十几万个节点。
浏览器根本承受不了这么大的压力。结果就是页面卡死、内存飙升、用户体验极差。想象一下用户打开页面要等几分钟才能操作,他们肯定会直接关掉网页。
如果后端不愿意做分页,我们可以自己在前端实现。
<template>
<div>
<table>
<tr v-for="item in currentData" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.email }}</td>
<td>{{ item.phone }}</td>
</tr>
</table>
<div class="pagination">
<button
v-for="page in totalPages"
:key="page"
@click="changePage(page)"
:class="{ active: currentPage === page }"
>
{{ page }}
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
// 模拟获取数据
const allData = ref([]);
const pageSize = 100; // 每页显示100条
const currentPage = ref(1);
// 获取数据
onMounted(async () => {
const response = await fetch('/api/big-data');
allData.value = await response.json();
});
// 计算总页数
const totalPages = computed(() => {
return Math.ceil(allData.value.length / pageSize);
});
// 当前页的数据
const currentData = computed(() => {
const start = (currentPage.value - 1) * pageSize;
const end = start + pageSize;
return allData.value.slice(start, end);
});
// 切换页面
const changePage = (page) => {
currentPage.value = page;
};
</script>
<style>
.pagination {
margin-top: 20px;
}
.pagination button {
margin: 0 5px;
padding: 5px 10px;
}
.pagination button.active {
background-color: #007bff;
color: white;
}
</style>分页的优点很明显:
实现简单,代码容易理解
用户体验可控,每次只看到部分数据
对浏览器压力小,不会卡顿
如果需要显示大量数据但又要求流畅滚动,虚拟滚动是最佳选择。它的原理是只渲染用户能看到的部分,看不见的数据先不渲染。
<template>
<div
class="virtual-list"
@scroll="handleScroll"
:style="{ height: containerHeight + 'px' }"
>
<div class="scroll-container" :style="{ height: totalHeight + 'px' }">
<div
v-for="item in visibleData"
:key="item.id"
class="list-item"
:style="{ transform: `translateY(${item.offset}px)` }"
>
<span>{{ item.name }}</span>
<span>{{ item.email }}</span>
<span>{{ item.phone }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
const allData = ref([]);
const itemHeight = 60; // 每行高度
const containerHeight = 600; // 容器高度
const scrollTop = ref(0); // 滚动位置
// 获取数据
onMounted(async () => {
const response = await fetch('/api/big-data');
allData.value = await response.json();
});
// 列表总高度
const totalHeight = computed(() => {
return allData.value.length * itemHeight;
});
// 可见区域的数据
const visibleData = computed(() => {
const startIndex = Math.floor(scrollTop.value / itemHeight);
const visibleCount = Math.ceil(containerHeight / itemHeight) + 5; // 多渲染几条避免空白
const endIndex = startIndex + visibleCount;
return allData.value.slice(startIndex, endIndex).map((item, index) => ({
...item,
offset: (startIndex + index) * itemHeight
}));
});
// 处理滚动
const handleScroll = (event) => {
scrollTop.value = event.target.scrollTop;
};
</script>
<style>
.virtual-list {
overflow-y: auto;
border: 1px solid #ddd;
}
.scroll-container {
position: relative;
}
.list-item {
position: absolute;
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
border-bottom: 1px solid #eee;
box-sizing: border-box;
}
</style>虚拟滚动的优势:
支持大量数据的流畅滚动
内存占用少,性能好
用户体验接近原生应用
// 避免 Vue 的响应式追踪,提升性能
const rawData = await fetch('/api/big-data');
const frozenData = Object.freeze(rawData);把繁重的数据处理放到后台线程,不影响主线程渲染。
// worker.js
self.onmessage = function(event) {
const { data, action } = event.data;
let result;
if (action === 'filter') {
result = data.filter(item => item.active);
} else if (action === 'sort') {
result = [...data].sort((a, b) => a.name.localeCompare(b.name));
}
self.postMessage(result);
};
// 在主线程中使用
const worker = new Worker('./worker.js');
worker.postMessage({ data: bigData, action: 'filter' });
worker.onmessage = function(event) {
const processedData = event.data;
// 更新界面
};如果确实需要显示大量数据,可以分批次渲染。
function renderBatch(data, batchSize = 500) {
let currentIndex = 0;
const visibleData = ref([]);
function renderNextBatch() {
const endIndex = Math.min(currentIndex + batchSize, data.length);
const batch = data.slice(currentIndex, endIndex);
visibleData.value = [...visibleData.value, ...batch];
currentIndex = endIndex;
if (currentIndex < data.length) {
// 在浏览器空闲时继续渲染下一批
requestIdleCallback(() => {
renderNextBatch();
});
}
}
renderNextBatch();
return visibleData;
}对于大数据量的搜索过滤,需要做防抖处理。
import { ref, computed } from 'vue';
const searchText = ref('');
const allData = ref([]);
// 防抖搜索
const debouncedSearch = useDebounce(searchText, 500);
const filteredData = computed(() => {
if (!debouncedSearch.value) return allData.value;
return allData.value.filter(item =>
item.name.toLowerCase().includes(debouncedSearch.value.toLowerCase()) ||
item.email.toLowerCase().includes(debouncedSearch.value.toLowerCase())
);
});
// 防抖函数
function useDebounce(value, delay) {
const debouncedValue = ref(value.value);
let timeout;
const update = (newValue) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
debouncedValue.value = newValue;
}, delay);
};
update(value.value);
return debouncedValue;
}优先和后端沟通:最好让后端支持分页,这是最根本的解决方案。
根据场景选择方案:
普通表格:用分页
聊天记录、日志查看:用虚拟滚动
数据分析:用Web Worker + 分批渲染
做好加载状态:大数据加载需要时间,要显示加载提示。
错误处理:网络请求可能失败,要有重试机制。
处理大数据的关键思路就是:不要一次性把所有数据都塞给浏览器。我们要学会分批次、按需加载。
分页加载:适合大多数场景,实现简单
虚拟滚动:适合需要流畅滚动的场景
分批渲染:适合需要显示大量数据的场景
Web Worker:适合复杂的数据处理
选择哪种方案要根据具体需求来定。在大多数情况下,分页已经足够满足需求了。如果对用户体验要求很高,再考虑虚拟滚动等更复杂的方案。
希望这些方案能帮你解决大数据渲染的烦恼!
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!
在react中是单向数据绑定,而在vue和augular中的特色是双向数据绑定。为什么会选择两种不同的机制呢?我猜测是两种不同的机制有不同的适应场景,查了一些资料后,总结一下。
双向数据绑定是非常重要的特性 —— 将JS模型与HTML视图对应,能减少模板编译时间同时提高用户体验。我们将学习在不使用框架的情况下,使用原生JS实现双向绑定 —— 一种为Object.observe
js判断数据类型的多种方法,主要包括:typeof、instanceof、 constructor、 prototype.toString.call()等,下面就逐一介绍它们的异同。
由于js为弱类型语言拥有动态类型,这意味着相同的变量可用作不同的类型。 typeof 运算符返回一个用来表示表达式的数据类型的字符串,目前typeof返回的字符串有以下这些: undefined、boolean、string、number、object、function、“symbol
在js中我们直接这样写typeof obj===‘object’有什么问题呢?发现Array, Object,null都被认为是一个对象了。如何解决这种情况,能保证判断obj是否为一个对象
js要处理十六进制,十进制,字符之间的转换,发现有很多差不多且书写不正确的方法.一个一个实践才真正清楚如何转换,现在来记录一下它们之间转换的方法。
奇数和偶数的判断是数学运算中经常碰到的问题,这篇文章主要讲解通过JavaScript来实现奇偶数的判断。2种判断方法:求余% 、&1
质数又称素数。指在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数。比如100以内共25个,js实现代码如下。
JavaScript自动类型转换真的非常常见,常用的一些便捷的转类型的方式,都是依靠自动转换产生的。比如 转数字 : + x 、 x - 0 , 转字符串 : \\\"\\\" + x 等等。现在总算知道为什么可以这样便捷转换。
XML是标准通用标记语言 (SGML) 的子集,非常适合 Web 传输。XML 提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。 这篇文章主要介绍Js中实现XML和String相互转化
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!