Vue3项目性能优化(示例)

前言:

在该篇中你能收获到,在vue3项目中性能优化的常见技巧(初级、进阶、高级)以及优化策略。

初级:

合理使用 key 属性:

  • 在使用v-for进行列表渲染时,为每个项指定唯一的key属性,例如 item.id ,key这个特殊的attribute主要作为Vue的虚拟Dom算法提示,有助于更高效的Dom重用。(这个就不用给示例了吧)

合理使用v-show替代v-if:

  • 在需要频繁切换显示或隐藏的元素上,例如tab标签栏的切换,使用v-show,可以避免频繁的Dom重新创建和销毁,提高性能。

v-if

v-show

 注意:同时监测4次 v-if与v-show的切换响应,可以看到v-show在频繁切换性能更胜一筹

合理使用computed计算属性:

 注意:此次切换所耗时间比先前缩短了1s左右,这里还可以再优化(名为瞬移),进阶中会换另一个例子讲。

使用动态组件:

  • 比如 Tab 界面,空间有限就不用vue3官网的例子,你们可以自己去里面的演练场试试。
<template>
  <div class="home">
    <div>
      <button @click="activeTab = 'tab1'">Tab 1</button>
      <button @click="activeTab = 'tab2'">Tab 2</button>
      <button @click="activeTab = 'tab3'">Tab 3</button>
    </div>
    <component :is="activeTab">
      <p v-if="activeTab === 'tab1'">Content for tab 1.</p>
      <p v-if="activeTab === 'tab2'">Content for tab 2.</p>
      <p v-if="activeTab === 'tab3'">Content for tab 3.</p>
    </component>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const activeTab = ref('tab1')
</script>

使用 keep-alive 内置组件:

  • 在多个组件间动态切换时缓存被移除的组件实例(当数据没有更新时,视图保持鲜活),这对性能有很大提升。keep-alive演练场

路由懒加载:

图片优化:

按需加载第三方库(请不要信任不靠谱的第三方库):

  • 这个看你安装的这个第三方库文档的按需引入配置就行

进阶:

灵活使用响应式:

继 ref() 和 reactive() 这两个API,Vue3新增了 shallowRef() 和 shallowReactive() 用于对大型数据结构的性能优化的API,目前国内的博客或者视频教程对此还没有讲到点上,我也没有思路如何调试验证,所幸,在stackoverflow上找到了答案,下面我将总结归纳vuejs3 - the difference between shallowReactive and shallowRef in vue3? - Stack Overflow

 

根据打印的结果,可以看见

当我改动了浅层x或者嵌套y.a,reactive均触发了更新。这是因为reactive会将对象进行深度包裹,使所有嵌套属性都具有响应式能力,这在大型数据结构中且只需改动浅层数据会有损性能

当我改动了浅层x,shallowReactive也触发了更新。这是因为shallowReactive和shallowRef都不能自动解包嵌套的ref,只有shallowReactive对浅层的ref进行自动解包为内部的具体值,而不再保持ref类型,因此也不用.value去访问值,shalllowReactive只对浅层属性具有响应性

在我的例子中,ref和shallowRef都没有触发更新

  • ref会对整个对象进行包装,并将其设置为一个ref类型的值。当修改整个对象时,才会触发更新。例如,r1.value = { x: 2 }会触发更新,而r1.value.x = 2则不会触发更新。

  • shallowRef只包裹对象的顶层属性。当修改这些顶层属性时,会触发更新。但是,如果修改了嵌套属性,shallowRef不会触发更新。例如,s1.value = { x: 2 }会触发更新,而s1.value.x = 2以及s1.value.y.a = 2不会触发更新。

需要注意的是:浅层数据结构应该只用于组件中的根级状态

使用Vue3的Teleport内置组件(瞬移/传送):

这部分是我这篇博客花最多时间的地方,可是最后测试性能对比,在使用teleport将tab页下的内容传送出去,比原来那样性能更差,而且会使得tabs栏失去粘性,不过可以手动添加粘性样式。。

我起初想法是利用teleport的特性,将tab页面下的内容放置在某个地方,使得更加灵活,盒子不用嵌套多层

 但是,这无疑是增加了Dom操作。。

原来没加teleport

加了teleport

结果:渲染时间差别不大,但是增加了阻塞进程 ,teleport内置组件的用法

虽然没能达到理想状态,但还是要勇于思考和尝试。

用自定义指令去实现防抖节流(Vue3+TS,支持所有eventType,建议收藏哈哈哈):

防抖节流一直是性能优化的有效手段,减轻服务器的负担,也可以减少后端老哥的问候;

一步步来,先看看我是在哪个文件封装的:

 在src/utils/index.ts暴露modules中instruction.ts(指令)文件中的所有方法,接下来看看代码

// instruction.ts

// 防抖指令封装
export const useDebounceDirective = (delay: number) => {
  return {
    beforeMount(el: HTMLElement, binding: any) {
      let timer: number;

      el.addEventListener(binding.arg, () => {
        clearTimeout(timer);
        timer = setTimeout(() => {
          binding.value();
        }, delay);
      });
    }
  };
};

// 节流指令封装
export const useThrottleDirective = (delay: number) => {
  return {
    beforeMount(el: HTMLElement, binding: any) {
      let throttled = false;

      el.addEventListener(binding.arg, () => {
        if (!throttled) {
          throttled = true;
          setTimeout(() => {
            binding.value();
            throttled = false;
          }, delay);
        }
      });
    }
  };
};
// utils/index.ts

export * from './modules/instruction';

组件中的代码

<template>
  <div>
    <input v-debounce:input="onInput" placeholder="输入内容"><br>
    <button v-debounce:click="onClickDebounce">点击防抖按钮</button>
    <button v-throttle:click="onClickThrottle">点击节流按钮</button>
  </div>
</template>

<script lang="ts" setup>
function onInput() {
  console.log('输入框防抖事件');
}

function onClickDebounce() {
  console.log('防抖事件');
}

function onClickThrottle() {
  console.log('节流事件')
}
</script>

<script lang="ts">
import { useDebounceDirective, useThrottleDirective } from "@/utils";

export default {
  directives: {
    debounce: useDebounceDirective(500),
    throttle: useThrottleDirective(1000)
  }
}
</script>


<style scoped>

</style>

亲测好用,而且很符合真实项目的需求(本来要插入有音频的视频。最后发现点击进入该博客会立刻播放音频,可能导致用户懵圈,平台还没解决该问题)

控制台打印效果

进行网络优化,让chatgpt总结一下:

缓存策略:根据具体情况设置以下 HTTP 头部字段来启用浏览器缓存和服务端缓存:

Cache-Control:用于指定资源的缓存行为。示例:

  • Cache-Control: public, max-age=3600:允许公共缓存,并设置最大缓存时间为 3600 秒(1 小时)。
  • Cache-Control: private, no-cache:不允许公共缓存,每次都需要与服务器确认。
  • Cache-Control: no-store:不允许任何形式的缓存。

ETag 和 If-None-Match:用于实现基于内容的缓存验证。示例:

  • ETag: "abcd1234":将唯一的标识符分配给资源的版本,并在后续请求中发送给客户端。
  • 客户端发送请求时,通过 If-None-Match 头部字段将之前的 ETag 值发送给服务器,如果资源未发生变化,服务器可返回状态码 304 Not Modified。

Last-Modified 和 If-Modified-Since:用于实现基于时间的缓存验证。示例:

  • Last-Modified: Wed, 01 Sep 2023 12:00:00 GMT:指示资源的最后修改时间。
  • 客户端发送请求时,通过 If-Modified-Since 头部字段将之前的修改时间发送给服务器,如果资源未发生变化,服务器可返回状态码 304 Not Modified。

高级:

使用CDN:

解决Internet网络拥挤的状况,提高用户访问网站的响应速度。已经有很多优秀文章了,我这里贴上一篇掘金的文章 -- 前端必需了解的CDN知识 - 掘金,有兴趣可以去学习一下。

使用webpack打包优化(vite的打包优化下次一定):

Configuration Languages | webpack 中文文档

一方面提高了开发的速度以及打包的速度,另一方面缩小了打包的体积,有助于干净的产品上线,减少服务器不必要的负担。

使用 css-minimizer-webpack-plugin 压缩css文件

  optimization: {
    minimizer: [
      new CssMinimizerPlugin()
    ],
  },

 使用 terser-webpack-plugin 对 TypeScript 编译后的 JavaScript 代码进行压缩

  optimization: {
    minimizer: [
      new TerserPlugin()
    ],
  },

使用 html-webpack-plugin 简化手动创建 HTML 文件、管理文件路径和资源引用的繁琐工作

  plugin: [
    new HtmlWebpackPlugin({
      title: 'vue3-webpack',
      template: path.join(__dirname, 'public/index.html'),
      minify: {
        removeComments: true,            // 移除注释
        removeRedundantAttributes: true, // 移除多余的属性
      }
    })
  ]

使用 mini-css-extract-plugin 提取项目中的 CSS 代码并生成一个独立的 CSS 文件

  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          MiniCssExtractPlugin.loader, 
          'css-loader', 
          'postcss-loader', 
          'sass-loader'
        ]
      }
    ]
  },

  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css' // 输出的 CSS 文件名
    })
  ],

使用 Tree shaking 静态分析代码,将未使用的代码从最终打包结果中去除

注意的是,Tree shaking 只能去除 ES6 模块导入和导出的代码

module.exports = {
  // ...其他配置项

  optimization: {
    usedExports: true
  }
};

使用 代码分割 优化技术,减小初始加载时所需的资源体积,提高页面加载速度和性能

使用动态导入的方式按需加载模块

// 按需加载模块
import('./module').then(module => {
  module.doSomething();
});

使用 import() 并配合 webpackPrefetch 或 webpackPreload 注释,告诉浏览器在空闲时间预先加载指定的模块

// 预加载模块
import(/* webpackPrefetch: true */ './module');

// 或者

// 预先加载模块
import(/* webpackPreload: true */ './module');

使用 SplitChunksPlugin (webpack 提供的内置插件),用于自动将公共代码提取到单独的文件中

module.exports = {
  // 其他

  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

 学会性能监控和调试,以及在写代码的时候,多思考每一行代码可能面临的问题,有没有更优的写法,这才是最佳优化性能法。

~ 你的点赞、评论、收藏是我发布优质文章的最大动力,感谢支持 ~

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值