亲宝软件园·资讯

展开

Vue 性能优化

WujieLi 人气:0

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

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

减少响应式使用

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 加载图片,比较常用三个可视区域判断方式是

在 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()],
    }),
  ],
}

加载全部内容

相关教程
猜你喜欢
用户评论