在 Vue 中渲染成千上万条数据时,直接渲染所有 DOM 节点会导致严重的性能问题(如内存占用高、渲染卡顿)。以下是几种常见的解决方法:
1. 使用虚拟滚动
只渲染可视区域内的元素,动态替换不可见区域的 DOM,极大减少实际渲染节点数。
npm install vue-virtual-scroller
<template>
<RecycleScroller
class="scroller"
:items="bigData"
:item-size="50"
key-field="id"
>
<template #default="{ item }">
<div class="item">{{ item.text }}</div>
</template>
</RecycleScroller>
</template>
<script>
import { RecycleScroller } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
export default {
components: { RecycleScroller },
data() {
return {
bigData: Array.from({ length: 10000 }, (_, i) => ({
id: i,
text: `Item ${i}`,
})),
};
},
};
</script>
<style scoped>
.scroller {
height: 500px;
}
.item {
height: 50px;
padding: 10px;
border-bottom: 1px solid #eee;
}
</style>
2. 数据冻结
通过 Object.freeze
冻结数据,避免 Vue 3 的响应式系统追踪变化,减少内存开销。
Object.freeze() 静态方法可以使一个对象被冻结。冻结对象可以防止扩展,并使现有的属性不可写入和不可配置。
export default {
data() {
return {
bigData: Object.freeze(
Array.from({ length: 10000 }, (_, i) => ({
id: i,
text: `Item ${i}`,
}))
),
};
},
};
3. 分片渲染
将数据分割成多个小块,利用浏览器的空闲时间(如 requestAnimationFrame
)逐步渲染,避免一次性渲染所有 DOM 节点导致主线程阻塞。
requestAnimationFrame() 方法会告诉浏览器你希望执行一个动画。它要求浏览器在下一次重绘之前,调用用户提供的回调函数。
<template>
<div>
<!-- 渲染已加载的数据 -->
<div v-for="item in visibleData" :key="item.id">{{ item.text }}</div>
<!-- 加载中的提示 -->
<div v-if="isLoading">加载中...</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
// 原始数据:1万条
const allData = ref(
Array.from({ length: 10000 }, (_, i) => ({
id: i,
text: `Item ${i}`,
}))
);
// 当前已渲染的数据
const visibleData = ref([]);
// 每次渲染的数据量
const chunkSize = 100;
// 当前渲染到的位置
const currentIndex = ref(0);
// 是否正在加载
const isLoading = ref(false);
// 分片渲染函数
const renderChunk = () => {
isLoading.value = true;
const end = currentIndex.value + chunkSize;
// 截取下一块数据
visibleData.value = allData.value.slice(0, end);
currentIndex.value = end;
if (end < allData.value.length) {
// 使用 requestAnimationFrame 继续渲染下一块
requestAnimationFrame(renderChunk);
} else {
isLoading.value = false;
}
};
// 组件挂载后开始渲染
onMounted(() => {
renderChunk();
});
return { visibleData, isLoading };
},
};
</script>
4. 优化 Vue 3 响应式数据
使用 shallowRef
或 shallowReactive
:减少深层响应式开销。
import { shallowRef } from 'vue';
export default {
setup() {
const bigData = shallowRef(
Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }))
);
return { bigData };
},
};
5. 分页加载
将数据分页加载,每次只渲染当前页的数据。
<template>
<div>
<div v-for="item in currentPageData" :key="item.id">{{ item.text }}</div>
<button @click="prevPage">上一页</button>
<button @click="nextPage">下一页</button>
</div>
</template>
<script>
export default {
data() {
return {
allData: Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` })),
currentPage: 1,
pageSize: 100,
};
},
computed: {
currentPageData() {
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.allData.slice(start, end);
},
},
methods: {
prevPage() { this.currentPage--; },
nextPage() { this.currentPage++; },
},
};
</script>