如何优化Vue应用程序的性能

399 阅读9分钟

页面加载性能与更新性能对比

在谈论优化 Vue 应用程序的性能时,应该解释两个主要方面:

  • 页面加载性能——这是关于应用程序在第一次有人访问时加载内容并变得可用的速度。它通常用核心网络生命体征指标最大内容绘画(LCP)来衡量。
  • 更新性能——这侧重于应用对用户操作的响应速度,例如,当有人在搜索框中键入时,列表更新的速度。它通常用核心网络生命体征指标到下一幅画的交互(INP)来衡量。

理想情况下,我们希望两者都最大化,但是不同的前端架构使得在每个领域实现性能目标变得更容易或更难。此外,您正在构建的应用程序类型对哪些性能方面应该是优先事项有很大影响。

本文将主要关注您可以做些什么来提高 Vue 应用程序的性能,如内置指令、插件和概念。我们不会涉及优化的其他方面,如超文本标记语言、CSS、API 改进。

性能测量工具

为了提升性能,我们首先需要一种方法来衡量它。幸运的是,有一些优秀的工具可以让您在本地开发、持续集成以及生产中审核/衡量 Vue 网站的性能。

用于检查生产中的负载性能:

可以通过以下方式在本地开发期间测试性能:

改善页面负载

这完全是关于应用程序加载内容并在第一次有人访问时变得可用的速度。

建筑学

如果快速的页面加载时间对您的应用程序至关重要,请尽量避免使其成为纯粹的客户端 SPA。

服务器最好发送已经包含用户需要的内容的超文本标记语言,因为仅客户端渲染就可以减慢内容变得可见的时间。服务器端渲染(SSR)或静态站点生成(SSG)可以帮助解决这个问题。

如果您的应用程序不需要太多复杂的交互性,您还可以使用传统的后端服务器来呈现超文本标记语言并添加 Vue 以进行客户端增强。

如果你需要你的主应用是一个 SPA,但有营销页面(如登陆页面或博客页面),考虑把它们分开!理想情况下,这些营销页面应该部署为静态超文本标记语言,使用静态站点生成最少的 JavaScript。

捆绑法

我们发送到客户端/浏览器的代码越少,我们的 Vue 应用程序的性能就越高。

  • 首选提供 ES 模块格式并且对摇树友好的依赖项。
  • 确保对任何未使用的代码进行树摇动。默认情况下,许多 Vue 插件和集成都带有此设置。
  • 如果您主要将 Vue 用于渐进式增强,并且希望避免构建步骤,请考虑使用petite-vue(仅 6kb)。
  • 检查依赖项的大小并评估它是否值得它提供的功能。像bundlejs.com这样的工具可以用于快速检查,但是使用您的实际构建设置进行测量总是最准确的。

代码分割

代码拆分是构建工具使用的一种技术,用于将应用程序包划分为更小、更易于管理的块。这些块可以按需或并行加载,通过减少最初需要加载的代码量来优化性能。

可以通过使用以下一种或全部方法来实现。

// belowTheFold.js 
function lazyLoadImport() { 
    return import("./belowTheFold.js"); 
}

belowTheFold.js及其依赖项将被拆分为一个单独的块,并且仅在调用lazyLoadImport()时加载。

延迟加载对于初始页面加载后不必要的功能特别有用。在 Vue 应用程序中,这可以通过使用 Vue 的异步组件功能来实现,使您能够将组件树拆分为根据需要按需加载的较小块。

import { defineAsyncComponent } from "vue"; 
const MyComponent = defineAsyncComponent(() => import("./MyComponent.vue"));

对于使用 Vue 路由器的应用程序,强烈建议使用延迟加载路由:

const router = createRouter({ 
// ... 
routes: [ { 
    path: "/tasks/:id", 
    component: () => import("./views/TaskDetails.vue")
    }, 
  ], 
});

Vue 路由器只会在第一次进入页面时获取组件代码,然后使用缓存版本。

提高应用反应性

这侧重于应用程序响应用户操作的速度,例如,当有人输入搜索框时,列表更新的速度有多快。

v-show vs v-if

无论是v-if还是v-show都控制元素的可见性,但它们的方式不同v-if完全从 DOM 中添加或删除元素,这可能更 performance-intensive。另一方面,v-show切换displayCSS 属性,使其成为需要经常显示或隐藏的元素的更有效选择。

<script setup>
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是一个内置指令,可用于呈现依赖于运行时数据但永远不需要更新的内容。它使用的整个子树将被跳过以进行所有未来的更新。

<!-- single element -->
<span v-once>This will never change: {{msg}}</span>

<!-- the element has children -->
<div v-once>
  <h1>Comment</h1>
  <p>{{msg}}</p>
</div>

<!-- `v-for` directive -->
<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>
import { ref } from 'vue';
import debounce from 'lodash-es/debounce';

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>
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 方法),这将允许我们缓存请求的结果并立即为用户返回,从而大大提高下一个条目的性能。

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作为其参数,并异步返回datauseSWRV还返回 2 个值:dataerror。当请求(fetcher)还没有完成时,data 将undefined

渲染大列表

前端应用程序中最常见的性能挑战之一是渲染大型列表。无论框架的效率如何,由于浏览器必须管理大量 DOM 节点,显示包含数千个项目的列表可能会很慢。要解决这个问题,您可以使用vue-virtual-scroller包。

避免内存泄漏

最常见的内存泄漏场景是在组件的初始加载中添加窗口事件侦听器,但在组件卸载后不删除它。

<script setup>
Import { onMounted, onUnmounted } from ‘vue’

onMounted(() => window.addEventListener('mousemove', doSomething))
onUnmounted(() => window.removeEventListener('mousemove', doSomething))
</script>

优化图像

通常,未优化的图像是性能不佳的主要来源。然而,这个问题可以通过使用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、大小和样式。该组件还检测来自大多数图像 CDN 和 CMS 的图像 URL,并且无需构建步骤即可调整图像大小。

总结情况

Vue 作为一个框架在设计时考虑到了性能,同时您还可以使用指令等内置可选功能来提高性能。实施前面部分的解决方案应该可以帮助您优化 Vue 应用程序的性能。

请记住,为了提高 Web 应用程序的性能,您还需要优化超文本标记语言、CSS 和 API 的性能,以实现性能一般的应用程序。