一、前言
前端优化是一个永恒的话题,每个前端开发者都希望自己的页面能够快速加载,给用户良好的体验,但是在实际开发中,我们经常会遇到一个棘手的问题,那就是首页白屏问题。
首页白屏问题是指用户打开页面后,需要等待很长时间才能看到页面内容,这会让用户感到不耐烦,甚至流失,造成白屏的原因有很多,比如网络延迟、JS 加载过多、渲染性能不佳等等,针对不同的原因,我们需要采取不同的优化策略。
今天和大家讨论一种常见的场景,以及其中的一种优化手段,这种优化手段可能很多人都不知道,但是它非常有效,可以大幅度提升用户的感知效率。
二、场景回顾
js
<template>
<div class="container">
<div v-for="n in 100">
<heavy-comp></heavy-comp>
</div>
</div>
</template>
<script setup>
import HeavyComp from './components/HeavyComp.vue';
</script>
这里我们导入了一个叫做 HeavyComp 的组件,这个组件里有很多很多的元素,特别是在大屏项目里元素可能特别密集,然后我们将这个组件循环了 100 次,每一次循环渲染一次,我们来看一下它的运行效果。
我们会发现有长时间的等待,最后一起把这些组件渲染出来,然后我们使用浏览器的调试工具 Performance 做一下性能分析:
可以看出 2686 毫秒在加载 JS,2795 毫秒在渲染,这样的页面对于用户而言是完全无法接受的,那么怎么来优化呢?
三、如何优化
其实主要的思路就是让这些组件一部分一部分的渲染,一起渲染压力太大,要分摊一下,先渲染其中一部分,让用户能够看到,然后再持续的渲染剩下的部分。虽然总的时间没有变化,甚至还有可能会增加,但是对于用户的感知而言,从用户打开页面到看到东西这段时间被大幅度的缩减了。这里我们先直接使用写好的优化方法:
js
<template>
<div class="container">
<div v-for="n in 100">
<!-- 在渲染组件的时候,我们可以利用 defer 函数来进行 if 判断 -->
<heavy-comp v-if="defer(n)"></heavy-comp>
</div>
</div>
</template>
<script setup>
import HeavyComp from './components/HeavyComp.vue';
// 导入一个我已经写好的函数
import { useDefer } from './useDefer';
// 这个函数返回一个方法
const defer = useDefer();
</script>
那么这个 defer 函数有什么作用呢?关于渲染帧大家应该都知道,就是在一个渲染时间线内,它是分成很多小段,每一段都是一个渲染帧,每一个渲染帧的间隔是 16.6 毫秒。
假如说第一个渲染帧叫第一次渲染,第二个渲染帧叫第二次渲染,依次类推,那么这个 defer(n)
表达的意思就是,目前渲染帧有没有超过这个 n
举个例子:比如说 defer(10)
,传入 10,表达的意思是当渲染的帧数是第一帧的时候,返回 false,第二帧的时候还是返回 false,只有当渲染帧大于等于第十帧的时候才返回 true,就这么简单。
当它返回 false 的时候,由于我们使用了 v-if
,那么这个组件它就不会渲染,换句话说,这里给它传一个 n,就表示它循环了 100 次,第一个组件的时候 n 是 1,就表示第一帧的时候渲染第一个组件,第二个组件的时候 n 是 2,就表示第二帧的时候渲染第二个组件,依此类推。
我们来看一下优化后页面的渲染:
我们发现很快就得到了响应,尽管总时间没有变化,因为它还在持续渲染,但是用户感知到它的效率已经大大提升了,我们再次用性能分析工具分析一下。
可以看到,渲染过程被分成了一帧一帧进行渲染,避免了页面长时间的卡顿,每一次渲染用户都能看到它的渲染结果。这有点像 React 的 fiber 架构,在 Vue3 中也有类似的概念,其实都是利用浏览器空闲时间来进行任务调度和拆分。
接下来我们来看看这个函数到底是怎么实现的。
四、useDefer
js
import { ref } from "vue";
// 函数接收一个参数,表示监测的最大帧数,这里默认值是 1000
// 如果说你渲染的东西特别多可以传入一个值
export function useDefer(maxFrameCount = 1000) {
// 然后开始计数
const frameCount = ref(0);
const refreshFrameCount = () => {
requestAnimationFrame(() => {
// 每一次 requestAnimationFrame 就计数加一
// 表示当前渲染的帧数变多了一帧
frameCount.value++;
// 只要当前帧数小于最大帧数就递归执行
if (frameCount.value < maxFrameCount) {
refreshFrameCount();
}
});
};
refreshFrameCount();
// 返回一个函数,接收传递进来的 n
return function (showInFrameCount) {
// 判断当前渲染的帧数有没有大于 n
return frameCount.value >= showInFrameCount;
};
}
五、总结
前端优化之白屏问题是一个非常重要而又常见的问题,我们需要根据不同场景采取不同策略。本文介绍了其中一种场景和优化方法,即利用 defer 函数来控制组件何时进行渲染,让页面内容逐步展现出来,而提高用户感知效率和体验。当然还有其他优化方法和技巧,比如使用骨架屏、预加载、懒加载、代码分割。
本文参考该篇文章,以及渡一教育袁老师的短视频讲解分享。