22个Vue优化技巧(项目实用)

目录
  • 代码优化
    • v-for 中使用 key
    • v-if/v-else-if/v-else 中使用 key
    • 合理的选择 v-if 和 v-show
    • 使用简单的 计算属性
    • functional 函数式组件(Vue2)
    • 拆分组件
    • 使用局部变量
    • 使用 KeepAlive
    • 事件的销毁
    • 图片加载
    • 采用合理的数据处理算法
    • 其他
  • 首屏/体积优化
    • 体积优化
    • 代码分割
    • 网络

演示代码使用 Vue3 + ts + Vite 编写,但是也会列出适用于 Vue2 的优化技巧,如果某个优化只适用于 Vue3 或者 Vue2,我会在标题中标出来。

代码优化

v-for 中使用 key

使用 v-for 更新已渲染的元素列表时,默认用就地复用策略;列表数据修改的时候,他会根据 key 值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素;

使用key的注意事项:

  • 不要使用可能重复的或者可能变化 key 值(控制台也会给出提醒)
  • 不要使用数组的 index 作为 key 值,因为如果在数组中插入一个元素时,其后面的元素 index 将会变化。
  • 如果数组中没有唯一的 key 值可用,可以考虑对其添加一个 key 字段,值为 Symbol() 即可保证唯一。

v-if/v-else-if/v-else 中使用 key

可能很多人都会忽略这个点

原因:默认情况下,Vue 会尽可能高效的更新 DOM。这意味着其在相同类型的元素之间切换时,会修补已存在的元素,而不是将旧的元素移除然后在同一位置添加一个新元素。如果本不相同的元素被识别为相同,则会出现意料之外的副作用。

如果只有一个 v-if ,没有 v-else 或者 v-if-else的话,就没有必要加 key 了

相对于 v-for 的 key, v-if/v-else-if/v-else 中的 key 相对简单,我们可以直接写入固定的字符串或者数组即可

  <transition>
    <button
      v-if="isEditing"
      v-on:click="isEditing = false"
    >
      Save
    </button>
    <button
      v-else
      v-on:click="isEditing = true"
    >
      Edit
    </button>
  </transition>
.v-enter-active, .v-leave-active {
  transition: all 1s;
}
.v-enter, .v-leave-to {
  opacity: 0;
  transform: translateY(30px);
}
.v-leave-active {
  position: absolute;
}

例如对于上面的代码, 你会发现虽然对 button 添加了 过渡效果, 但是如果不添加 key 切换时是无法触发过渡的
v-for 和 v-if 不要一起使用(Vue2)

此优化技巧仅限于Vue2,Vue3 中对 v-for 和 v-if 的优先级做了调整

这个大家都知道

永远不要把 v-if 和 v-for 同时用在同一个元素上。 引至 Vue2.x风格指南

原因是 v-for 的 优先级高于 v-if,所以当它们使用再同一个标签上是,每一个渲染都会先循环再进行条件判断

注意: Vue3 中 v-if 优先级高于 v-for,所以当 v-for 和 v-if 一起使用时效果类似于 Vue2 中把 v-if 上提的效果

例如下面这段代码在 Vue2 中是不被推荐的,Vue 也会给出对应的警告

<ul>
  <li v-for="user in users" v-if="user.active">
    {{ user.name }}
  </li>
</ul>

我们应该尽量将 v-if 移动到上级 或者 使用 计算属性来处理数据

<ul v-if="active">
  <li v-for="user in users">
    {{ user.name }}
  </li>
</ul>

如果你不想让循环的内容多出一个无需有的上级容器,那么你可以选择使用 template 来作为其父元素,template 不会被浏览器渲染为 DOM 节点
如果我想要判断遍历对象里面每一项的内容来选择渲染的数据的话,可以使用 computed 来对遍历对象进行过滤

// js
let usersActive = computed(()=>users.filter(user => user.active))

// template
<ul>
    <li v-for="user in usersActive">
      {{ user.name }}
    </li>
</ul>

合理的选择 v-if 和 v-show

v-if 和 v-show 的区别相比大家都非常熟悉了; v-if 通过直接操作 DOM 的删除和添加来控制元素的显示和隐藏;v-show 是通过控制 DOM 的 display CSS熟悉来控制元素的显示和隐藏
由于对 DOM 的 添加/删除 操作性能远远低于操作 DOM 的 CSS 属性
所以当元素需要频繁的 显示/隐藏 变化时,我们使用 v-show 来提高性能。
当元素不需要频繁的 显示/隐藏 变化时,我们通过 v-if 来移除 DOM 可以节约掉浏览器渲染这个的一部分DOM需要的资源

使用简单的 计算属性

应该把复杂计算属性分割为尽可能多的更简单的 property。

易于测试
当每个计算属性都包含一个非常简单且很少依赖的表达式时,撰写测试以确保其正确工作就会更加容易。

易于阅读
简化计算属性要求你为每一个值都起一个描述性的名称,即便它不可复用。这使得其他开发者 (以及未来的你) 更容易专注在他们关心的代码上并搞清楚发生了什么。

更好的“拥抱变化”
任何能够命名的值都可能用在视图上。举个例子,我们可能打算展示一个信息,告诉用户他们存了多少钱;也可能打算计算税费,但是可能会分开展现,而不是作为总价的一部分。
小的、专注的计算属性减少了信息使用时的假设性限制,所以需求变更时也用不着那么多重构了。

引至 Vue2风格指南

computed 大家后很熟悉, 它会在其表达式中依赖的响应式数据发送变化时重新计算。如果我们在一个计算属性中书写了比较复杂的表达式,那么其依赖的响应式数据也任意变得更多。当其中任何一个依赖项变化时整个表达式都需要重新计算

let price = computed(()=>{
  let basePrice = manufactureCost / (1 - profitMargin)
  return (
      basePrice -
      basePrice * (discountPercent || 0)
  )
})

当 manufactureCost、profitMargin、discountPercent 中任何一个变化时都会重新计算整个 price。
但是如果我们改成下面这样

let basePrice = computed(() => manufactureCost / (1 - profitMargin))
let discount = computed(() => basePrice * (discountPercent || 0))
let finalPrice = computed(() => basePrice - discount)

如果当 discountPercent 变化时,只会 重新计算 discount 和 finalPrice,由于 computed 的缓存特性,不会重新计算 basePrice

functional 函数式组件(Vue2)

注意,这仅仅在 Vue2 中被作为一种优化手段,在 3.x 中,有状态组件和函数式组件之间的性能差异已经大大减少,并且在大多数用例中是微不足道的。因此,在 SFCs 上使用 functional 的开发人员的迁移路径是删除该 attribute,并将 props 的所有引用重命名为 $props,将 attrs 重命名为 $attrs。

优化前

<template>
    <div class="cell">
        <div v-if="value" class="on"></div>
        <section v-else class="off"></section>
    </div>
</template>

<script>
export default {
    props: ['value'],
}
</script>

优化后

<template functional>
    <div class="cell">
        <div v-if="props.value" class="on"></div>
        <section v-else class="off"></section>
    </div>
</template>

<script>
export default {
    props: ['value'],
}
</script>
  • 没有this(没有实例)
  • 没有响应式数据

拆分组件

什么?你写的一个vue文件有一千多行代码?🤔
合理的拆分组件不仅仅可以优化性能,还能够让代码更清晰可读。单一功能原则嘛

源自 https://slides.com/akryum/vueconfus-2019#/4/0/3

优化前

<template>
  <div :style="{ opacity: number / 300 }">
    <div>{{ heavy() }}</div>
  </div>
</template>

<script>
export default {
  props: ['number'],
  methods: {
    heavy () { /* HEAVY TASK */ }
  }
}
</script>

优化后

<template>
  <div :style="{ opacity: number / 300 }">
    <ChildComp/>
  </div>
</template>

<script>
export default {
  props: ['number'],
  components: {
    ChildComp: {
      methods: {
        heavy () { /* HEAVY TASK */ }
      },
      render (h) {
        return h('div', this.heavy())
      }
    }
  }
}
</script>

由于 Vue 的更新是组件粒度的,虽然每一帧都通过数据修改导致了父组件的重新渲染,但是 ChildComp 却不会重新渲染,因为它的内部也没有任何响应式数据的变化。所以优化后的组件不会在每次渲染都执行耗时任务

使用局部变量

优化前

<template>
  <div :style="{ opacity: start / 300 }">{{ result }}</div>
</template>

<script>
import { heavy } from '@/utils'

export default {
  props: ['start'],
  computed: {
    base () { return 42 },
    result () {
      let result = this.start
      for (let i = 0; i < 1000; i++) {
        result += heavy(this.base)
      }
      return result
    }
  }
}
</script>

优化后

<template>
  <div :style="{ opacity: start / 300 }">
    {{ result }}</div>
</template>

<script>
import { heavy } from '@/utils'

export default {
  props: ['start'],
  computed: {
    base () { return 42 },
    result () {
      const base = this.base
      let result = this.start
      for (let i = 0; i < 1000; i++) {
        result += heavy(base)
      }
      return result
    }
  }
}
</script>

这里主要是优化前后的组件的计算属性 result 的实现差异,优化前的组件多次在计算过程中访问 this.base,而优化后的组件会在计算前先用局部变量 base,缓存 this.base,后面直接访问 base。

那么为啥这个差异会造成性能上的差异呢,原因是你每次访问 this.base 的时候,由于 this.base 是一个响应式对象,所以会触发它的 getter,进而会执行依赖收集相关逻辑代码。类似的逻辑执行多了,像示例这样,几百次循环更新几百个组件,每个组件触发 computed 重新计算,然后又多次执行依赖收集相关逻辑,性能自然就下降了。

从需求上来说,this.base 执行一次依赖收集就够了,把它的 getter 求值结果返回给局部变量 base,后续再次访问 base 的时候就不会触发 getter,也不会走依赖收集的逻辑了,性能自然就得到了提升。

引至 揭秘 Vue.js 九个性能优化技巧

使用 KeepAlive

在一些渲染成本比较高的组件需要被经常切换时,可以使用 keep-alive 来缓存这个组件
而在使用 keep-alive 后,被 keep-alive 包裹的组件在经过第一次渲染后,的 vnode 以及 DOM 都会被缓存起来,然后再下一次再次渲染该组件的时候,直接从缓存中拿到对应的 vnode 和 DOM,然后渲染,并不需要再走一次组件初始化,render 和 patch 等一系列流程,减少了 script 的执行时间,性能更好。

注意: 滥用 keep-alive 只会让你的应用变得更加卡顿,因为他会长期占用较大的内存

事件的销毁

当一个组件被销毁时,我们应该清除组件中添加的 全局事件 和 定时器 等来防止内存泄漏
Vue3 的 HOOK 可以让我们将事件的声明和销毁写在一起,更加可读

function scrollFun(){ /* ... */}
document.addEventListener("scroll", scrollFun)

onBeforeUnmount(()=>{
  document.removeEventListener("scroll", scrollFun)
})

Vue2 依然可以通过 $once 来做到这样的效果,当然你也可以在 optionsAPI beforeDestroy 中销毁事件,但是我更加推荐前者的写法,因为后者会让相同功能的代码更分散

function scrollFun(){ /* ... */}
document.addEventListener("scroll", scrollFun)

this.$once('hook:beforeDestroy', ()=>{
  document.removeEventListener("scroll", scrollFun)
})
function scrollFun(){ /* ... */}

export default {
  created() {
    document.addEventListener("scroll", scrollFun)
  },
  beforeDestroy(){
    document.removeEventListener("scroll", scrollFun)
  }
}

图片加载

图片懒加载:适用于页面上有较多图片且并不是所有图片都在一屏中展示的情况,vue-lazyload 插件给我们提供了一个很方便的图片懒加载指令 v-lazy

但是并不是所有图片都适合使用懒加载,例如 banner、相册等 更加推荐使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。

采用合理的数据处理算法

这个相对比较考验 数据结构和算法 的功底
例如一个将数组转化为多级结构的方法

/**
 * 数组转树形结构,时间复杂度O(n)
 * @param list 数组
 * @param idKey 元素id键
 * @param parIdKey 元素父id键
 * @param parId 第一级根节点的父id值
 * @return {[]}
 */
function listToTree (list,idKey,parIdKey,parId) {
    let map = {};
    let result = [];
    let len = list.length;

    // 构建map
    for (let i = 0; i < len; i++) {
        //将数组中数据转为键值对结构 (这里的数组和obj会相互引用,这是算法实现的重点)
        map[list[i][idKey]] = list[i];
    }

    // 构建树形数组
    for(let i=0; i < len; i++) {
        let itemParId = list[i][parIdKey];
        // 顶级节点
        if(itemParId === parId) {
            result.push(list[i]);
            continue;
        }
        // 孤儿节点,舍弃(不存在其父节点)
        if(!map[itemParId]){
            continue;
        }
        // 将当前节点插入到父节点的children中(由于是引用数据类型,obj中对于节点变化,result中对应节点会跟着变化)
        if(map[itemParId].children) {
            map[itemParId].children.push(list[i]);
        } else {
            map[itemParId].children = [list[i]];
        }
    }
    return result;
}

其他

除了上面说的方法以外还有很多优化技巧,只是我在项目并不是太常用🤣

  • 冻结对象(避免不需要响应式的数据变成响应式)
  • 长列表渲染-分批渲染
  • 长列表渲染-动态渲染(vue-virtual-scroller
  • ...

首屏/体积优化

我在项目中关于首屏优化主要有以下几个优化方向

  • 体积
  • 代码分割
  • 网络

体积优化

  • 压缩打包代码: webpack 和 vite 的生产环境打包默认就会压缩你的代码,这个一般不需要特殊处理,webpack 也可以通过对应的压缩插件手动实现
  • 取消 source-map: 可以查看你的打包产物中是否有 .map 文件,如果有你可以将 source-map 的值设置为false或者空来关闭代码映射(这个占用的体积是真的大)
  • 打包启用 gizp 压缩: 这个需要服务器也开启允许 gizp 传输,不然启用了也没啥用( webpack 有对应的 gzip 压缩插件,不太版本的 webpack 压缩插件可能不同,建议先到官网查询)

代码分割

代码分割的作用的将打包产物分割为一个一个的小产物,其依赖 esModule。所以当你使用 import() 函数来导入一个文件或者依赖,那么这个文件或者依赖就会被单独打包为一个小产物。 路由懒加载 和 异步组件 都是使用这个原理。

  • 路由懒加载
  • 异步组件

对于 UI库 我一般不会使用按需加载组件,而是比较喜欢 CDN 引入的方式来优化。

网络

  • CDN: 首先就是上面的说的 CDN 引入把,开发阶段使用本地库,通过配置 外部扩展(Externals) 打包时来排除这些依赖。然后在 html 文件中通过 CDN 的方式来引入它们
  • Server Push: HTTP2已经相对成熟了;经过上面的 CDN 引入,我们可以对网站使用 HTTP2 的 Server Push 功能来让浏览器提前加载 这些 CDN 和 其他文件。
  • 开启 gzip: 这个上面已经说过了,其原理就是当客户端和服务端都支持 gzip 传输时,服务端会优先发送经过 gzip 压缩过的文件,然后客户端接收到在进行解压。
  • 开启缓存: 一般我使用的是协商缓存,但是这并不适用于所有情况,例如对于使用了 Server Push 的文件,就不能随意的修改其文件名。所以我一般还会将生产的主要文件固定文件名

到此这篇关于22个Vue优化技巧(项目实用)的文章就介绍到这了,更多相关Vue优化技巧内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue.js九个性能优化技巧(值得收藏)

    目录 Functional components Child component splitting Local variables Reuse DOM with v-show KeepAlive Deferred features Time slicing Non-reactive data Virtual scrolling 总结 参考资料 这篇文章主要参考了 Vue.js 核心成员Guillaume Chau在 19 年美国的 Vue conf 分享的主题:9 Performance se

  • vue webpack打包优化操作技巧

    临近春节,公司很多同事都提前回家过年,剩余人员根据禅道去修改bug,当bug修正完毕以后,我们需要重新打包给运维,上测试服给测试同事提测,但是由于项目本体比较庞大,所以打包时间太过漫长(二十五分钟以上:sob:),所以有了打包优化的想法(其实想法早就有了,但是因为平时工作计划比较充实,所以一直没有去完成这个工作),这次正好有时间,所以去重新考虑了这个问题! webpack是react项目标配的打包工具,和NPM搭配起来使用管理模块实在非常方便.   webapck 把所有的静态资源都看做是一个

  • 22个Vue优化技巧(项目实用)

    目录 代码优化 v-for 中使用 key v-if/v-else-if/v-else 中使用 key 合理的选择 v-if 和 v-show 使用简单的 计算属性 functional 函数式组件(Vue2) 拆分组件 使用局部变量 使用 KeepAlive 事件的销毁 图片加载 采用合理的数据处理算法 其他 首屏/体积优化 体积优化 代码分割 网络 演示代码使用 Vue3 + ts + Vite 编写,但是也会列出适用于 Vue2 的优化技巧,如果某个优化只适用于 Vue3 或者 Vue2,

  • vue项目中一定会用到的性能优化技巧

    目录 引言 性能优化标准 Lighthouse 通用常规优化手段 通用性能优化分析 FCP(First Contentful Paint) LCP(Largest Contentful Paint) SpeedIndex(速度指数) 排查性能瓶颈 分析包内容 利用chorme devtool 的代码覆盖率 针对vue 的特殊优化 图片懒加载 虚拟滚动 vue 中的函数式组件 利用v-show .KeepAlive 复用dom 分批渲染组件 最后 引言 提起性能优化 很多人眼前浮现的面试经验是不是

  • 优化RequireJS项目的相关技巧总结

    本文将演示如何合并与压缩一个基于RequireJS的项目.本文中将用到苦干个工具,这其中就包括Node.js. 因此,如果你手头上还没有Node.js可以点击此处下载一个. 动机 关于RequireJS已经有很多文章介绍过了.这个工具可以将你的JavaScript代码轻易的分割成苦干个模块(module)并且保持你的代码模块化与易维护性.这样,你将获得一些具有互相依赖关系的JavaScript文件.仅仅需要在你的HTML文档中引用一个基于RequireJS的脚本文件,所有必须的文件都将会被自动引

  • vue使用技巧及vue项目中遇到的问题

    这里给大家分享一下vue中的一些技巧,希望对大家有用处.(话不多说上代码) 1,vue路由拦截浏览器后退实现表单保存类似需求(为了防止用户突然离开,没有保存已输入的信息.) //在路由组件中: mounted(){ }, beforeRouteLeave (to, from, next) { if(用户已经输入信息){ //出现弹窗提醒保存表单,或者自动后台为其保存 }else{ next(true);//用户离开 } 请参考vue文档全局钩子和组件钩子 2,路由懒加载写法: // 我所采用的方

  • vue开发中后台系统复杂表单优化技巧

    目录 引言 表单校验 使用computed进行表单校验优化 表单拆分 表单兄弟组件的数据通信问题 引言 在中后台系统的日常开发中,表单必不可少,当表单内容比较多,例如有上百个字段(这一点都不夸张,血淋淋的现实)时,代码往往也变得复杂且难以维护,加上各种动态联动的表单校验,无疑让我们的页面开发过程雪上加霜,本文将结合自己平时的开发习惯,分享一下在大表单开发中如何处理复杂的表单校验,以及如何对表单进行拆分,减少单个文件堆积过多的代码内容. 表单校验 <template> <el-form r

  • Python 代码性能优化技巧分享

    如何进行 Python 性能优化,是本文探讨的主要问题.本文会涉及常见的代码优化方法,性能优化工具的使用以及如何诊断代码的性能瓶颈等内容,希望可以给 Python 开发人员一定的参考. Python 代码优化常见技巧 代码优化能够让程序运行更快,它是在不改变程序运行结果的情况下使得程序的运行效率更高,根据 80/20 原则,实现程序的重构.优化.扩展以及文档相关的事情通常需要消耗 80% 的工作量.优化通常包含两方面的内容:减小代码的体积,提高代码的运行效率. 改进算法,选择合适的数据结构 一个

  • 基于JavaScript 性能优化技巧心得(分享)

    JavaScript 作为当前最为常见的直译式脚本语言,已经广泛应用于 Web 应用开发中.为了提高Web应用的性能,从 JavaScript 的性能优化方向入手,会是一个很好的选择. 本文从加载.上下文.解析.编译.执行和捆绑等多个方面来讲解 JavaScript 的性能优化技巧,以便让更多的前端开发人员掌握这方面知识. 什么是高性能的 JavaScript 代码? 尽管目前没有高性能代码的绝对定义,但却存在一个以用户为中心的性能模型,可以用作参考:RAIL模型. 响应 如果你的应用程序能在1

  • JetBrains IntelliJ IDEA 配置优化技巧

    本教程基于 JetBrains IntelliJ IDEA 2018.3.6 编写,高版本未经测试,或有不兼容,请见谅! JetBrains IntelliJ IDEA 分为两个版本:旗舰版(Ultimate)和社区版(Community).旗舰版收费(30天免费使用时间,功能齐全):社区版(永久免费,功能简陋). 1.目录结构解释 bin:容器,执行文件和启动参数等 help:快捷键文档和其他帮助文档 jre64:64 位 Java 运行环境 lib:IDEA 依赖的类库 license:各个

  • Spring Cloud OpenFeign 的五个优化技巧

    目录 一.超时优化 1.设置Ribbon超时时间 2.设置OpenFeign超时时间 二.请求连接优化 1.引入Apache HttpClient依赖 2.开启Apache HttpClient使用 三.数据压缩 四.负载均衡优化 五.日志级别优化 总结 前言: OpenFeign 是 Spring 官方推出的一种声明式服务调用和负载均衡组件.它的出现就是为了替代已经进入停更维护状态的 Feign(Netflix Feign),同时它也是 Spring 官方的顶级开源项目.我们在日常的开发中使用

随机推荐