Bootstrap

利用 Vue 组合式 API 与 requestAnimationFrame 优化大量元素渲染

在 Vue 项目开发中,当面临需要渲染大量元素的场景时,如何高效地处理渲染过程以避免性能瓶颈是一个关键问题。本文将深入探讨一种基于 Vue 3 的 <script setup> 语法糖和 requestAnimationFrame 的解决方案,通过自定义 useDefer 组合式函数来逐步渲染大量元素,提升页面性能与用户体验。

一、问题引入

在前端开发中,直接渲染大量元素可能会导致页面卡顿甚至无响应。例如,当我们需要在页面上展示 10000 个数据项时,如果一次性将所有元素都渲染到页面上,浏览器需要处理大量的 DOM 操作,这对于性能来说是一个巨大的挑战。用户可能会看到长时间的白屏或者页面滚动不流畅等问题,严重影响了用户体验。

二、useDefer 组合式函数解析

1. 函数定义与基本变量

import {onUnmounted, ref} from "vue";

export function useDefer(maxCount = 100) {
    const frameCount = ref(0);
    let rafId;

这里首先从 Vue 中引入了 onUnmountedrefref 用于创建一个响应式数据,frameCount 就是一个响应式的变量,用于记录当前已经经过的动画帧数。rafId 则用于存储 requestAnimationFrame 返回的 ID,以便后续在组件卸载时能够取消动画帧的请求。

2. updateFrameCount 函数 - 动画帧更新逻辑

function updateFrameCount() {
    rafId = requestAnimationFrame(() => {
        frameCount.value++;
        if (frameCount.value >= maxCount) {
            return;
        }
        updateFrameCount();
    });
}

updateFrameCount 函数是整个逻辑的核心部分。它使用 requestAnimationFrame 来创建一个动画帧的循环。在每一帧中,首先将 frameCount 的值加 1,表示已经经过了一个新的动画帧。然后检查 frameCount 是否已经达到了预设的 maxCount。如果没有达到,就继续递归调用 updateFrameCount,这样就形成了一个持续运行的动画帧更新循环,直到达到 maxCount 为止。

3. 组件卸载时的清理工作

onUnmounted(()=>{
    cancelAnimationFrame(rafId);
})

当组件卸载时,使用 onUnmounted 钩子函数来取消之前通过 requestAnimationFrame 创建的动画帧请求。这是非常重要的一步,避免了在组件已经不需要更新时仍然占用浏览器资源,防止内存泄漏等问题。

4. defer 函数 - 元素渲染控制

return function defer(n) {
    return frameCount.value >= n;
}

最后返回的 defer 函数接受一个参数 n,它会根据当前的 frameCount 值与传入的 n 进行比较。如果 frameCount 的值大于等于 n,则表示当前元素可以进行渲染,返回 true;否则返回 false。这样就可以在模板中根据元素的索引来控制元素的渲染时机,实现逐步渲染的效果。

三、组件中的使用

在 Vue 组件中,使用 useDefer 组合式函数非常简单:

<script setup>
import {useDefer} from '@/hooks/useDefer'
const defer = useDefer()
</script>

<template>
  <div v-for="(item,index) in 10000">
    <div class="box" v-if="defer(index)">
      {{ item }}
    </div>
  </div>
</template>

通过引入 useDefer 函数并获取 defer 实例,在模板的 v-for 循环中,使用 v-if 指令结合 defer 函数来根据元素的索引控制每个 div 元素的渲染时机。这样,不是一次性渲染所有 10000 个元素,而是按照 requestAnimationFrame 设定的帧率逐步渲染,减轻了浏览器的负担,提高了页面的响应速度和流畅度。

四、总结与优化思考

通过自定义的 useDefer 组合式函数,我们有效地解决了大量元素渲染时可能出现的性能问题。这种方式利用了 requestAnimationFrame 与 Vue 的组合式 API 的优势,实现了元素的逐步渲染。然而,在实际应用中,还可以进一步优化。例如,可以根据页面的可见区域动态调整 maxCount 的值,只渲染当前可见区域附近的元素,对于不可见区域的元素延迟渲染甚至不渲染,进一步提高性能。同时,也可以考虑添加更多的参数来灵活控制渲染策略,以适应不同的业务场景和性能需求。希望本文能够为 Vue 开发者在处理大量元素渲染问题时提供一种有效的思路和解决方案,让我们能够构建出更加高效、流畅的 Vue 应用程序。

五、整体JS代码

import {onUnmounted, ref} from "vue";

export function useDefer(maxCount = 100) {
    const frameCount = ref(0);
    let rafId;
    function updateFrameCount() {
        rafId = requestAnimationFrame(() => {
            frameCount.value++;
            if (frameCount.value >= maxCount) {
                return;
            }
            updateFrameCount();
        });
    }

    updateFrameCount();
    onUnmounted(()=>{
        cancelAnimationFrame(rafId);
    })
    return function defer(n) {
        return frameCount.value >= n;
    }
}
;