草庐IT

Vue 中直接上手的性能优化方案

web老猴子 2023-04-09 原文

最近使用 Vue 开发的过程中使用到一些对于性能有所提升的编码方式,所以特别梳理出来,可以作为后续 Vue 开发的编码规范使用

性能优化方案主要分为三类,下面就详细讲讲这三类优化方案的应用

  • 减少响应式使用
  • 减少 DOM 渲染
  • 减少打包体积

减少响应式使用

Vue 中使用最方便的就是响应式的变量,在读取(get)对象属性的时候收集副作用函数(effect)依赖,在写入(set)属性时取出副作用函数依赖执行,但是收集依赖、触发依赖执行毕竟都会影响到性能,所以在明确知道不需要使用响应式变量的场景下,就应该减少响应式变量的使用

1. 使用 computed 缓存计算结果

computed 和普通方法的区别在于:computed 会缓存计算结果,只有当计算的内容改变的时候才会重新计算,而普通方法每次都会重新计算。所以对于有计算逻辑的取值,建议尽量都通过 computed 来封装一层

比如下面这个示例就是简单的将 props 通过 computed 封装一层后共 template 使用

const getTooltipStyle = computed((): CSSProperties => {return {color: props.color,fontSize: props.fontSize,};
}); 

2. 本地化响应式变量

根据 Vue 响应式变量的原理,每次访问响应式数据时,都会收集依赖,所以在需要频繁使用响应式变量的时候,可以先将响应式变量用一个本地变量存储,转换为一个非响应式的变量

在 Vue3 中可以使用 unref 这个 api 来获取到响应式变量参数本身(Vue2 中直接通过 this 赋值就好)

const tableData = ref([]) 

const unrefTableData = unref(tableData) // 本地化变量后再做大量操作
unrefTableData.forEach(item => {// 具体操作
}) 

3. 函数式组件(Vue2)

函数式组件是指:只接受一些 prop 参数,无响应式数据,无实例的组件,主要应用在创建简单的展示组件,比如标题 header、纯展示的表单等等。因为没有响应式数据和实例,所以初始化速度比普通有状态的组件快很多,并且还支持返回多个节点

在 Vue2 中声明函数式组件的方式如下

<!-- template 中的声明方式 -->
<template functional>
</template>

<!-- jsx 中的声明方式 -->
Vue.component("list", {
	functional: true,
}) 

但是在 Vue3 中,有状态的组件性能已经大大提升,和无状态组件(函数式组件)几乎没有差异,并且有状态组件也支持了返回多个节点,所以官方也移除了 functional 定义函数式组件的方式,注意 Vue3 中是不兼容 Vue2 的函数式组件定义,所以如果未来打算升级 Vue3 的小伙伴就不建议使用函数式组件了

减少 DOM 渲染压力

1. DOM 频繁切换展示的情况使用 v-show

这是一个老身长谈的优化方案了,原理在于 v-ifv-show 实现方式的区别,对于 v-if 在不符合条件的情况下不会渲染 DOM 节点,对于 v-show 则是将各个条件情况都渲染出来,在通过 display: block / none 进行切换,所以在频繁切换 DOM 展示情况的场景下,使用 v-show 的性能会相对更好,比如一个可编辑单元格需要频繁切换编辑和保存后的状态的时候

v-show 也不是没有缺点,因为会把各个分支情况都提前渲染出来,如果节点很多并且不需要频繁切换状态,用 v-if 会是更好的选择

2. keep-alive 缓存组件状态

在 Vue 中切换组件时,组件内部的状态也会丢失,比如我们在填写一个表单的时候,切换到另外一个组件填写其他信息,在切换回之前的表单组件后,原来填写的信息会被刷新掉,这种情况下就会使用到 keep-alive 组件缓存组件状态

比较常用的做法是在 <router-view> 标签内嵌套一层 <transition> 标签增加组件切换时的过渡动画效果,再嵌套一层 <keep-alive> 标签缓存组件状态,最后使用 <component> 渲染动态组件或者元素

<router-view><template #default="{ Component, route }"><transition>	<keep-alive> <component :is="Component" :key="route.path" />	</keep-alive></transition></template>
</router-view> 

3. 路由懒加载

我们都知道 Vue 是单页面页面应用,如果在首屏加载的时候就把所有需要使用的路由都加载出来的话,那就太浪费性能了,所以使用懒加载的方式加载路由,减少首屏加载的压力,才是更合理的方案

在 vue-router 中使用路由懒加载需要通过箭头函数返回一个 import 组件的路径,这样在运行到这个组件的时候,才会运行 import 编译加载组件

const form: AppRouteRecordRaw = {path: "/basicForm",name: "BasicForm",component: () => import("/@/views/form/index.vue"),meta: {title: "基础表单",},
};

export default form; 

4. 图片懒加载

图片使用懒加载的原因和路由懒加载类似,都是为了减少不必要的渲染。比如我们有一张很长的页面有很多数据或者图片需要展示,而显示屏幕的可视高度却是固定的,所以在屏幕高度外的内容完全可以等到页面需要的时候再加载,从而减少了可是屏幕区域内的渲染压力

图片懒加载的原理是:判断图片出现在当前窗口时,将 data-src 替换为 src 加载图片,比较常用三个可视区域判断方式是

  • img.getBoundingClientRect().top < document.documentElement.clientHeight(元素相对于窗口位置 < 窗体高度)
  • IntersectionObserver api,当其监听到目标元素的可见部分到达屏幕高度内,执行指定的回调函数
  • loading="lazy" 属性(目前兼容性不是特别好,参考Lazy loading - Web 性能

在 Vue 中使用图片懒加载推荐使用 vue-lazyload 这个插件,直接通过 v-lazy 这个指令就可以实现图片懒加载的效果

<ul><li v-for="img in list"><img v-lazy="img.src" ></li>
</ul> 

5. 组件销毁时要清除定时器、EventListener

有时我们会在项目中开启 setTimeout 来定时触发一些事件,比如定时提醒表单保存之类的需求,如果在离开组件时没有及时清除掉定时器或者是 EventListener ,很多页面堆积起来很容易造成页面卡顿和内存泄漏

常见的方案是在离开组件之前的 onBeforeUnmount 生命周期钩子中清除掉定时器和 EventListener

onBeforeUnmount(() => {try {instance?.destroy?.();} catch (error) {instanceRef.value = null;}
}) 

在清除 EventListener 要注意:移除相同的函数。以下第一种情况不能清理掉 click 事件,因为它们是不同的函数对象,需要使用第二种指向相同函数对象的方式清除

// 这种情况不生效,因为指向的是不同函数对象
input.addEventListener("click", () => console.log("Hello"))
input.removeEventListener("click", () => console.log("Hello"))

// 此时指向相同的函数对象才能清理掉 EventListener 事件
function handler() {console.log("Hello")
}

input.addEventListener("click", () => handler)
input.removeEventListener("click", () => handler) 

6. 列表使用唯一 key

这个主要是和 diff 算法的效率有关,所以我也把它作为减少 DOM 渲染压力的一个方案。在我们使用 v-for 循环渲染内容的时候,需要为每个组件分配一个 id,这样在组件内容有更新的时候,diff 算法通过 id 能够更高效的找到变化的节点,让 dom 渲染更迅速。同时需要注意分配的 id 最好不是数组的 index,因为一旦增加或减少数组元素,index 也会发生变化,这样就失去 id 的效果了

<template v-for="schema in getSchema" :key="schema.field"><form-item:schema="schema":form-props="getProps":all-default-values="defaultValueRef"/>
</template> 

减少打包体积

1. 开启 gzip 压缩

gzip 一种文件压缩的格式,比较适合文本文件的压缩,通常会缩小两倍以上的体积,所以用在代码文件的压缩上非常合适

我们现在使用的打包工具还是 webpack,在 webpack 中开启 gzip 打包的话可以使用 compression-webpack-plugin 这个插件,具体配置如下

const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {configureWebpack : {plugins: [new CompressionPlugin({test: /\.(js|css|json|html)&/,tereshold: 10 * 1024, // 超过 10 k 才压缩})]}
} 

开启 gzip 除了要在代码中增加配置外,还需要服务端的支持,在前端中比较常用的是 Nginx,在 Nginx 中开启 gzip 压缩的主要配置参数如下

#开启和关闭gzip模式
gzip on;

#gizp压缩起点,文件大于10k才进行压缩
gzip_min_length 10k;

# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间,一般为 5,再大效果就不明显了
gzip_comp_level 5;

# 进行压缩的文件类型。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript ;

#nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
gzip_static on

# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;

# 设置压缩所需要的缓冲区大小,以4k为单位,如果文件为7k则申请2*4k的缓冲区 
gzip_buffers 2 4k;

# 设置gzip压缩针对的HTTP协议版本
gzip_http_version 1.1; 

2. 按需引入第三方组件

我们平时使用的 UI 组件一般都是大而全的,我们的项目中很少会全部使用到,所以按需引入第三方组件,能够有效减少应用包体积

以我们现在使用的 Element Plus 组件为例,使用 unplugin-vue-componentsunplugin-auto-import 这两个插件来实现(参考官方教程

首先引入两个插件

pnpm i -D unplugin-vue-components unplugin-auto-import 

然后再 Webpack 配置两个插件即可

// webpack.config.js
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

module.exports = {// ...plugins: [AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],
} 

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

有关Vue 中直接上手的性能优化方案的更多相关文章

  1. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  2. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  3. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  4. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  5. Ruby 守护进程和 JRuby - 备选方案 - 2

    我有一个应用程序正在从Ruby迁移到JRuby(由于需要通过Java提供更好的Web服务安全支持)。我使用的gem之一是daemons创建后台作业。问题在于它使用fork+exec来创建后台进程,但这对JRuby来说是禁忌。那么-是否有用于创建后台作业的替代gem/wrapper?我目前的想法是只从shell脚本调用rake并让rake任务永远运行......提前致谢,克里斯。更新我们目前正在使用几个与Java线程相关的包装器,即https://github.com/jmettraux/rufus-scheduler和https://github.com/philostler/acts

  6. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  7. ruby - 如何找到我的 Ruby 应用程序中的性能瓶颈? - 2

    我编写了一个Ruby应用程序,它可以解析来自不同格式html、xml和csv文件的源中的大量数据。我如何找出代码的哪些区域花费的时间最长?有没有关于如何提高Ruby应用程序性能的好资源?或者您是否有任何始终遵循的性能编码标准?例如,你总是用加入你的字符串吗?output=String.newoutput或者你会使用output="#{part_one}#{part_two}\n" 最佳答案 好吧,有一些众所周知的做法,例如字符串连接比“#{value}”慢得多,但是为了找出您的脚本在哪里消耗了大部分时间或比所需时间更多,您需要进行分

  8. (附源码)vue3.0+.NET6实现聊天室(实时聊天SignalR) - 2

    参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍  介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。  内容有:    ①:Hub模型的方法介绍    ②:服务器端代码介绍    ③:前端vue3安装并调用后端方法    ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke()  去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on

  9. STM32的HAL和LL库区别和性能对比 - 2

    LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L

  10. ruby - GC.disable 的任何性能缺点? - 2

    是否存在GC.disable会降低性能的情况?只要我使用的是真正的RAM而不是交换内存,就可以这样做吗?我正在使用MRIRuby2.0,据我所知,它是64位的,并且使用的是64位的Ubuntu:ruby2.0.0p0(2013-02-24revision39474)[x86_64-linux]Linux[redacted]3.2.0-43-generic#68-UbuntuSMPWedMay1503:33:33UTC2013x86_64x86_64x86_64GNU/Linux 最佳答案 GC.disable将禁用垃圾回收。像rub

随机推荐