前端如何渲染几千几万条数据而不卡顿?

在 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 响应式数据

使用 shallowRefshallowReactive:减少深层响应式开销。

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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值