3839 字
19 分钟

vue性能优化

概述#

Vue 在大多数常见场景下性能都是很优秀的,通常不需要手动优化。然而,总会有一些具有挑战性的场景需要进行针对性的微调。在本节中,我们将讨论用 Vue 开发的应用在性能方面该注意些什么。

首先,让我们区分一下 web 应用性能的两个主要方面:

  • 页面加载性能:首次访问时,应用展示出内容与达到可交互状态的速度。这通常会用 Google 所定义的一系列 Web 指标 (Web Vitals) 来进行衡量,如最大内容绘制 (Largest Contentful Paint,缩写为 LCP) 和首次输入延迟 (First Input Delay,缩写为 FID)。

  • 更新性能:应用响应用户输入更新的速度。比如当用户在搜索框中输入时结果列表的更新速度,或者用户在一个单页面应用 (SPA) 中点击链接跳转页面时的切换速度。

虽然最理想的情况是将两者都最大化,但是不同的前端架构往往会影响到在这些方面是否能达到更理想的性能。此外,你所构建的应用的类型极大地影响了你在性能方面应该优先考虑的问题。因此,优化性能的第一步是为你的应用类型确定合适的架构:

优化流程#

分析选项#

为了提高性能,我们首先需要知道如何衡量它。在这方面,有一些很棒的工具可以提供帮助:

用于生产部署的负载性能分析:

用于本地开发期间的性能分析:

页面加载优化#

页面加载优化有许多跟框架无关的方面 - 这份 web.dev 指南提供了一个全面的总结。这里,我们将主要关注和 Vue 相关的技巧。

选用正确的架构#

如果你的用例对页面加载性能很敏感,请避免将其部署为纯客户端的 SPA,而是让服务器直接发送包含用户想要查看的内容的 HTML 代码。纯客户端渲染存在首屏加载缓慢的问题,这可以通过服务器端渲染 (SSR)静态站点生成 (SSG) 来缓解。查看 SSR 指南 以了解如何使用 Vue 实现 SSR。如果应用对交互性要求不高,你还可以使用传统的后端服务器来渲染 HTML,并在客户端使用 Vue 对其进行增强。

如果你的主应用必须是 SPA,但还有其他的营销相关页面 (落地页、关于页、博客等),请单独部署这些页面!理想情况下,营销页面应该是包含尽可能少 JS 的静态 HTML,并用 SSG 方式部署。

包体积与 Tree-shaking 优化#

一个最有效的提升页面加载速度的方法就是压缩 JavaScript 打包产物的体积。当使用 Vue 时有下面一些办法来减小打包产物体积:

  • 尽可能地采用构建步骤

    • 如果使用的是相对现代的打包工具,许多 Vue 的 API 都是可以被 tree-shake 的。举例来说,如果你根本没有使用到内置的 组件,它将不会被打包进入最终的产物里。Tree-shaking 也可以移除你源代码中其他未使用到的模块。

    • 当使用了构建步骤时,模板会被预编译,因此我们无须在浏览器中载入 Vue 编译器。这在同样最小化加上 gzip 优化下会相对缩小 14kb 并避免运行时的编译开销。

  • 在引入新的依赖项时要小心包体积膨胀!在现实的应用中,包体积膨胀通常因为无意识地引入了过重的依赖导致的。

    • 如果使用了构建步骤,应当尽量选择提供 ES 模块格式的依赖,它们对 tree-shaking 更友好。举例来说,选择 lodash-es 比 lodash 更好。

    • 查看依赖的体积,并评估与其所提供的功能之间的性价比。如果依赖对 tree-shaking 友好,实际增加的体积大小将取决于你从它之中导入的 API。像 bundlejs.com 这样的工具可以用来做快速的检查,但是根据实际的构建设置来评估总是最准确的。

  • 如果你只在渐进式增强的场景下使用 Vue,并想要避免使用构建步骤,请考虑使用 petite-vue (只有 6kb) 来代替。

代码分割#

代码分割是指构建工具将构建后的 JavaScript 包拆分为多个较小的,可以按需或并行加载的文件。通过适当的代码分割,页面加载时需要的功能可以立即下载,而额外的块只在需要时才加载,从而提高性能。

像 Rollup (Vite 就是基于它之上开发的) 或者 webpack 这样的打包工具可以通过分析 ESM 动态导入的语法来自动进行代码分割:

// lazy.js 及其依赖会被拆分到一个单独的文件中
// 并只在 `loadLazy()` 调用时才加载
function loadLazy() {
return import('./lazy.js')
}

懒加载对于页面初次加载时的优化帮助极大,它帮助应用暂时略过了那些不是立即需要的功能。在 Vue 应用中,这可以与 Vue 的异步组件搭配使用,为组件树创建分离的代码块:

import {defineAsyncComponent} from 'vue'
// 会为 Foo.vue 及其依赖创建单独的一个块
// 它只会按需加载
//(即该异步组件在页面中被渲染时)
const Foo = defineAsyncComponent(() => import('./Foo.vue'))

对于使用了 Vue Router 的应用,强烈建议使用异步组件作为路由组件。Vue Router 已经显性地支持了独立于 defineAsyncComponent 的懒加载。查看懒加载路由了解更多细节。

更新优化#

Props 稳定性#

在 Vue 之中,一个子组件只会在其至少一个 props 改变时才会更新。思考以下示例:

<ListItem v-for="item in list" :id="item.id" :active-id="activeId" />

<ListItem> 组件中,它使用了 idactiveId 两个 props 来确定它是否是当前活跃的那一项。虽然这是可行的,但问题是每当 activeId 更新时,列表中的每一个 <ListItem> 都会跟着更新!

理想情况下,只有活跃状态发生改变的项才应该更新。我们可以将活跃状态比对的逻辑移入父组件来实现这一点,然后让 <ListItem> 改为接收一个 active prop:

<ListItem v-for="item in list" :id="item.id" :active="item.id === activeId" />

现在,对于大多数的组件来说,activeId 改变时,它们的 active prop 都会保持不变,因此它们无需再更新。总结一下,这个技巧的核心思想就是让传给子组件的 props 尽量保持稳定。

v-once#

v-once 是一个内置的指令,可以用来渲染依赖运行时数据但无需再更新的内容。它的整个子树都会在未来的更新中被跳过。查看它的 API 参考手册可以了解更多细节。

v-memo#

v-memo 是一个内置指令,可以用来有条件地跳过某些大型子树或者 v-for 列表的更新。查看它的 API 参考手册可以了解更多细节。

计算属性稳定性#

从 3.4 开始,计算属性仅在其计算值较前一个值发生更改时才会触发副作用。例如,以下 isEven 计算属性仅在返回值从 true 更改为 false 时才会触发副作用,反之亦然:

const count = ref(0)
const isEven = computed(() => count.value % 2 === 0)
watchEffect(() => console.log(isEven.value)) // true
// 这将不会触发新的输出,因为计算属性的值依然为 `true`
count.value = 2
count.value = 4

这减少了非必要副作用的触发。但不幸的是,如果计算属性在每次计算时都创建一个新对象,则不起作用:

const computedObj = computed(() => {
return {
isEven: count.value % 2 === 0,
}
})

由于每次都会创建一个新对象,因此从技术上讲,新旧值始终不同。即使 isEven 属性保持不变,Vue 也无法知道,除非它对旧值和新值进行深度比较。这种比较可能代价高昂,并不值得。

相反,我们可以通过手动比较新旧值来优化。如果我们知道没有变化,则有条件地返回旧值:

const computedObj = computed(oldValue => {
const newValue = {
isEven: count.value % 2 === 0,
}
if (oldValue && oldValue.isEven === newValue.isEven) {
return oldValue
}
return newValue
})

通用优化方法#

代码层面优化#

  1. 组件拆分:将大组件拆分成更小的组件,以提高重用性和可维护性,并减少不必要的渲染。
  2. 计算属性缓存:使用计算属性而不是方法,因为计算属性是基于它们的依赖进行缓存的。
  3. 合理使用v-if和v-showv-if是“真正”的条件渲染,因为它确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-show只是简单地切换元素的CSS display属性。因此,频繁切换时用v-show,条件不经常改变时用v-if
  4. 避免不必要的嵌套:减少不必要的Vue组件嵌套层级,可以减少组件的渲染时间。
  5. 事件代理:对于可能有很多子元素的列表,使用事件代理来减少事件监听器的数量。
  6. 节流与防抖节流与防抖
  7. JavaScript脚本放到页面底部:不影响页面加载的JavaScript放到底部延迟加载运行。
  8. 减少dom层级和数量:HTML 中标签元素越多,标签的层级越深,浏览器解析 DOM 并制作到浏览器中所花的时间就越长,所以应尽或许坚持 DOM 元素简洁和扁平化的层级。
  9. 减少重排重绘:减少重排重绘可以降低浏览器重新加载节点的次数。

虚拟DOM优化#

  1. key的使用:在v-for循环中使用唯一的key,这有助于Vue更快地识别列表中元素的身份,从而优化DOM的更新过程。
  2. 函数式组件:对于没有状态(响应式数据)和实例方法(计算属性、watch、methods)的组件,可以使用函数式组件,因为它们渲染起来更快。

资源加载优化#

  1. 代码分割:使用Vue CLI等构建工具进行代码分割,按需加载组件,减少初始加载时间。
  2. 异步组件:对于一些不需要立即渲染的组件,可以使用异步组件,以减少应用程序的初始大小。
  3. icon使用精灵图:默认情况下页面中有几张图片就会发起几次请求,所以我们可以将图片全部合成在一张图中,然后通过操作 CSSbackground 属性,控制背景的位置以及大小,来展示需要的部分。这样可以减少 HTTP 请求压力
  4. 资源压缩:压缩资源提高访问速度
  5. 减少http请求次数:将多个 CSS 和 JavaScript 文件合并为一个文件,可以减少 HTTP 请求次数,从而提高页面加载速度。同时,使用浏览器缓存可以避免每次请求相同的文件。

性能监控和调试#

  1. Vue Devtools:使用Vue Devtools来监控组件的性能,查找不必要的渲染和性能瓶颈。
  2. 性能分析:使用浏览器的性能分析工具,如Chrome的Performance tab,来分析Vue应用的性能。

服务器优化#

  1. 静态资源使用CDN:用户与服务器的物理距离对响应时间也有影响。把内容部署在多个地理位置分散的服务器上能让用户更快地载入页面, CDN就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。
  2. 开启http2:HTTP2带来了非常大的加载优化,所以在做优化上可以用HTTP2代替HTTP1。
  3. 分域存放资源:由于浏览器同一域名并行下载数有限,利用多域名主机存放静态资源,增加并行下载数,缩短资源加载时间。
  4. 减少重定向:重定向也会额外消耗请求资源。

其他#

  1. 使用keep-alive缓存组件:对于不需要频繁重新渲染的组件,可以使用<keep-alive>来缓存它们的状态。
  2. 合理使用v-once:对于不会改变的静态内容,可以使用v-once进行一次性渲染,减少后续的渲染工作量。
  3. 服务端渲染(SSR):对于需要快速加载的应用,可以使用服务端渲染来加快首屏加载速度。
  4. 使用Web Workers:对于耗时的计算任务,可以考虑使用Web Workers来避免阻塞主线程。
  5. webpack或其他工具构建优化:使用工具进行代码加载优化。

补充#

性能测试网站#

  • WebPageTest:在线web性能测试工具(https://www.webpagetest.org), 提供多地点测试。他只能测试已经发布了的网站。输入需要测试的网页地址,点击start test按钮就开始测试了,可以选择测试地理位置,测试的浏览器等。
  • Web Page Analyzer :它是一种强大的速度测试工具,为您提供了详细现场分析以及如何改进的建议。它提供许多功能包括网页速度报告,全球报告,外部文件计算,加载时间,分析和建议等。
  • GT Matrix:GTmetrix可以帮助您开发更快,更高效的网站体验,能全方位改善网站体验。它对你的网站性能继续分级,并提供可行的建议,以解决发现的任何问题。
  • Pingdom:Pingdom的是一个非常棒的工具,它可以帮助用户通过生成大量报道,如页面大小,浏览器缓存,性能等级等等来确定网站的加载时间。它允许您从不同的位置跟踪历史表现和行为测试。可以设置每隔几分钟测试你的网站,每周发送邮件总结一周内你的网站平均加载时间,可以用来实时监控网站的响应性能。
  • 36全球网站性能测试 :工具一开始是起飞页虎翼商城的内部测试工具,专门用来测试网站的国外访问速度。虽然国内也不乏其它可以测试网站速度的工具,但他们都只能给出简单的CURL和WGET的时间,并没有详细的渲染时间和可交互时间,更没有SEO评测,可用性评测。大家茶余饭后没事的时候可以来测试一下网站速度,讨论提升网站速度的心得。

参考#

vue性能优化
https://march7th.online/blog/posts/0025-vue性能优化/
作者
Yiguo
发布于
2024-07-17
许可协议
CC BY-NC-SA 4.0
最后更新于 2024-07-17,距今已过 479 天

部分内容可能已过时

目录