来源文章How To Optimize Performance In Vue Apps
此文作者是 Nuxt.js 核心团队的 Jakub Andrzejewski
译者注
这篇文章发布在 debugbear 上。工具我们可以不用,但是优化的思路可以借鉴一番🤣
其中有许多链接也指向 debugbear,后续可能会把这一系列文章全部翻译为中文。
页面加载性能与更新性能
当谈论在 Vue 应用中优化性能时,有两个主要方面应该被解释:
- 页面加载性能:这主要关于应用首次访问时内容加载速度以及变得可用的快慢。通常使用核心 Web 指标最大内容渲染(LCP)来衡量。
- 更新性能:这关注应用对用户操作的响应速度,例如,当有人输入搜索框时列表更新有多快。通常使用核心 Web 指标交互到下一次绘制(INP)来衡量。
理想情况下,我们希望最大化两者,但不同的前端架构使得在每个领域达到性能目标更容易或更难。此外,你正在构建的应用类型对哪些性能方面应该是优先级有很大影响。
本文将主要关注您可以采取哪些措施来提高 Vue 应用性能,例如内置指令、插件和概念。我们不会涉及其他优化方面,如 HTML、CSS、API 改进。
性能测量工具
为了提高性能,我们首先需要一种衡量它的方法。幸运的是,有一些出色的工具允许您在本地开发、持续集成以及生产环境中审计/衡量 Vue 网站的性能。
用于在生产中检查负载性能:
在本地开发期间测试性能可以通过以下方式完成:
最后,您还可以使用以下方式在持续集成中衡量性能:
提升页面加载速度
这是关于应用首次访问时加载内容和变得可用的速度。
构建
如果快速页面加载时间对您的应用至关重要,请尽量避免将其做成纯客户端的 SPA。
服务器发送已包含用户所需内容的 HTML 更好,因为仅客户端渲染会减慢内容可见的时间。服务器端渲染(SSR)或静态站点生成(SSG)可以解决这个问题。要了解更多关于 SSR 如何提高 Vue 应用程序性能的信息,请查看这篇文章。
如果你的应用不需要很多复杂的交互性,你也可以使用传统的后端服务器来渲染 HTML,并添加 Vue 进行客户端增强。
如果您需要主应用是一个单页应用(SPA),但又有营销页面(如着陆页或博客页面),考虑将它们分开!理想情况下,这些营销页面应以静态 HTML 的形式部署,使用静态站点生成器,并尽可能减少 JavaScript 的使用。
Bundling 捆绑包
我们向客户端/浏览器发送的代码越少,我们的 Vue 应用程序的性能就越好。
- 优先选择提供 ES 模块格式且对 tree-shaking 友好的依赖项。
- 确保删除任何未使用的代码。许多 Vue 插件和集成默认都带有这种设置。
- 如果您主要使用 Vue 进行渐进增强并希望避免构建步骤,请考虑使用 petite-vue(仅 6kb)。
- 检查依赖项的大小并评估它提供的功能是否值得。可以使用如 bundlejs.com 之类的工具进行快速检查,但使用您实际的构建设置进行测量始终是最准确的。
代码拆分
代码拆分是构建工具采用的一种技术,用于将应用程序包拆分为更小、更易于管理的块。这些块可以按需或并行加载,通过减少初始需要加载的代码量来优化性能。
可以通过使用以下一种或所有方法来实现。
function lazyLoadImport() {
return import("./belowTheFold.js");
}
belowTheFold.js
及其依赖将被拆分为单独的块,并且仅在调用 lazyLoadImport()
时才加载。
懒加载对于初始页面加载后不是立即必需的功能特别有用。在 Vue 应用中,可以通过使用 Vue 的异步组件功能来实现,这使您能够将组件树拆分为更小的块,按需加载。
import { defineAsyncComponent } from "vue";
const MyComponent = defineAsyncComponent(() => import("./MyComponent.vue"));
对于使用 Vue Router 的应用,强烈建议使用懒加载路由:
const router = createRouter({
// ...
routes: [
{
path: "/tasks/:id",
component: () => import("./views/TaskDetails.vue")
},
],
});
Vue Router 仅在首次进入页面时获取组件代码,然后使用缓存版本。
提升应用响应性
这关注的是应用对用户操作的响应速度,例如,当有人输入搜索框时列表更新有多快。
v-show 和 v-if
v-if
和 v-show
都控制元素的可见性,但它们以不同的方式做到这一点。 v-if
完全从 DOM 中添加或删除元素,这可能会更消耗性能。另一方面, v-show
切换 display
CSS 属性,使其成为需要频繁显示或隐藏的元素的一个更高效的选择。
<script setup lang="ts">
import { ref } from "vue";
const show = ref(false);
</script>
<template>
<div>
<button @click="show = !show">
Toggle
</button>
<div v-show="show">
Toggled display property
</div>
<div v-if="show">
Element added to the DOM
</div>
</div>
</template>
考虑使用 v-show
来表示频繁切换以使状态变化更高效的元素。
无需后续更新的内容渲染
v-once
是一个内置指令,可用于渲染依赖于运行时数据但无需更新的内容。它所使用的整个子树在所有未来的更新中都将被跳过。
<!-- 单个元素 -->
<span v-once>
This will never change: {{msg}}
</span>
<!-- 包含子元素 -->
<div v-once>
<h1>Comment</h1>
<p>{{msg}}</p>
</div>
<!-- `v-for` 指令 -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>
有条件的跳过大型列表更新
v-memo
是一个内置指令,可用于有条件地跳过大型子树或 v-for 列表的更新。
<div v-memo="[valueA, valueB]">
...
</div>
当组件重新渲染时,如果 valueA 和 valueB 保持不变,则将跳过此 <div>
及其子组件的所有更新。
防抖
在管理用户输入,如搜索查询或表单提交时,重要的是要防抖或节流事件以防止性能问题。
防抖延迟函数执行,直到自上次调用以来经过指定时间,而节流确保函数在给定时间间隔内只执行一次。
<script setup lang="ts">
import debounce from "lodash-es/debounce";
import { ref } from "vue";
const value = ref("");
const search = debounce(async () => {
await api.getSearchResults(value); // mocked search operation
}, 400);
watch(value, (newVal) => {
search();
});
</script>
<template>
<input v-model="value">
</template>
在这个例子中,搜索功能将在用户停止输入后仅执行 400 毫秒,以最小化 API 调用次数并提高性能。
总体改进
除了页面加载时间和应用响应性之外,还有几个一般性的改进,你可以添加到你的应用中,使其整体性能更优。
避免将静态数据变成响应式
Vue 响应性是一个强大的工具,它允许我们跟踪属性的变化并在引用时修改其值。但有时,我们可能会错误地将静态属性变为响应性,如下面的示例所示:
<script setup lang="ts">
import { ref } from "vue";
const milisecondsInAnHour = ref(3600000);
</script>
该 milisecondsInAnHour
变量的值不会随时间改变,因此没有必要使其变为响应式。
数据量很大时使用浅反应性
Vue 的响应式系统默认是深层次的,这使得状态管理直观,但处理大数据集时可能会引入开销。这是因为每个属性访问都会触发代理陷阱以进行依赖跟踪。
为了减轻这种情况,Vue 提供了 shallowRef()
和 shallowReactive()
,允许您选择退出深度响应性。这些浅层 API 仅在根级别创建响应式状态,而不会影响嵌套对象。
const shallowArray = shallowRef([
/* big list of deep objects */
]);
// this won't trigger updates...
shallowArray.value.push(newObject);
// this does:
shallowArray.value = [...shallowArray.value, newObject];
缓存
导航前后通常会导致触发所有渲染视图和组件的逻辑。这可能导致触发对已应经可用的数据的多次请求,因为我们已经访问过这个页面。
对于此类内容,我们可以利用缓存的概念(特别是“陈旧-验证更新”方法),这将允许我们缓存请求的结果并立即将其返回给用户,从而大大提高后续条目的性能。
陈旧-验证策略(Stale-while-revalidate)是一种 HTTP 缓存策略,其中浏览器检查缓存的响应是否仍然新鲜。如果是,浏览器将提供缓存的 内容;如果不是,它将通过从网络获取新鲜响应的同时向用户提供服务陈旧内容来“重新验证”。
在 Vue 中,我们可以像以下这样使用 SWRV 库:
import useSWRV from "swrv";
const { data, error } = useSWRV("/api/user", fetcher);
在这个例子中,Vue 组合式 useSWRV 接受一个 key 和一个 fetcher 函数。 key 是请求的唯一标识符,通常是 API 的 URL。 fetcher 接受 key 作为其参数并异步返回 data 。 useSWRV 也返回 2 个值: data 和 error 。当请求(fetcher)尚未完成时,数据将是 undefined 。
译者注
我们也可以使用 tanstack query 来作为请求的缓存层
渲染长列表
前端应用中最常见的性能挑战之一是渲染大量列表。无论框架多么高效,由于浏览器需要管理的 DOM 节点数量庞大,显示包含数千项的列表可能会很慢。为了解决这个问题,您可以使用 vue-virtual-scroller 包。
避免内存泄露
内存泄露最常见的情况是在组件的初始化添加监听器,但在组件卸载后没有移除它
要记得在组件卸载时移除事件监听器
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
onMounted(() => window.addEventListener('mousemove', doSomething))
onUnmounted(() => window.removeEventListener('mousemove', doSomething))
</script>
优化图片
通常,未优化的图像是性能不佳的主要原因。然而,通过使用如 unpic/vue 这样的解决方案,这个问题可以轻松解决,unpic/vue 是一个针对 Vue 的高性能响应式图像组件。
<script setup lang="ts">
import { Image } from "@unpic/vue";
</script>
<template>
<image
src="https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg"
layout="constrained"
width="800"
height="600"
alt="A lovely bath"
/>
</template>
它生成一个响应式的 <img>
标签,遵循最佳实践,具有正确的 srcset、sizes 和样式。该组件还可以检测大多数图像 CDN 和 CMS 中的图像 URL,并且可以在不进行构建步骤的情况下调整图像大小。
摘要
Vue 作为框架,在设计时考虑了性能,同时您还可以使用内置的可选功能,如指令,使其性能更上一层楼。实施前几节中的解决方案可以帮助您优化 Vue 应用的性能。
请记住,为了提高您 Web 应用程序的性能,您还需要优化 HTML、CSS 和 API 的性能,以实现整体性能良好的应用程序。
附加内容:使用 DebugBear 监控 Vue 应用
通过使用 DebugBear,您可以获取关于您 Vue 应用性能的所有有价值数据,关于竞争对手的信息,以及从多个位置运行测试的能力,所有这些都可以在一个仪表板上完成!在此处免费试用!
除了仪表板之外,您还可以使用 HTML 大小分析器来查看哪些内容正在导致您的文档大小。查找如内联图片、大型 hydration 状态或代码重复等问题。
这可以帮助您发现未优化的代码和可以改进的地方。
额外资源
如果您想了解更多关于这些概念的信息,请查看以下文章:
评论区
评论加载中...