>  기사  >  웹 프론트엔드  >  Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁

Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁

青灯夜游
青灯夜游앞으로
2022-02-24 11:35:073612검색

Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 이 기사에서는 개발 중 12가지 Vue 성능 최적화 팁을 공유하겠습니다. 도움이 되기를 바랍니다.

Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁

성능 최적화는 모든 개발자가 직면하게 되는 문제입니다. 특히 경험이 점점 더 강조되고 점점 경쟁이 심화되는 환경에서 우리 개발자에게는 반복만 완료하면 되며 그것만으로는 충분하지 않습니다. 좋은 기능을 가지고 있다는 점에서 가장 중요한 것은 더 많은 사람들이 사용하고 싶어하고, 사용자들이 더 즐겁게 사용할 수 있도록 제품을 좋게 만드는 것입니다. 이 또한 우리 개발자들의 가치와 능력을 반영하는 것이 아닐까요?

성능 문제에 주의를 기울이고 제품 경험을 최적화하는 것은 몇 가지 무해한 버그를 수정하는 것보다 훨씬 더 가치가 있습니다.

이 기사에는 Vue 프로젝트의 일상적인 개발에 대한 몇 가지 팁이 기록되어 있습니다. 이제 시작하겠습니다! [관련 추천: vuejs 영상 튜토리얼]

1. 긴 목록 성능 최적화

1. 응답 없음

예를 들어 회원 목록, 제품 목록 등은 동적 변경 없이 순수한 데이터만 표시됩니다. 시나리오에서는 데이터에 대해 반응형 처리를 수행할 필요가 없으므로 렌더링 속도가 크게 향상될 수 있습니다

예를 들어 Object.freeze()를 사용하여 개체를 고정하면 MDN에서는 개체가 고정된다고 설명합니다. 이 방법으로는 수정이 불가능합니다. 즉, 이 객체에 새 속성을 추가할 수 없고, 기존 속성을 삭제할 수 없으며, 객체의 기존 속성에 대한 열거 가능성, 구성 가능성 및 쓰기 가능성을 수정할 수 없으며, 기존 속성의 값도 수정할 수 없습니다. 속성은 수정할 수 없으며, 객체 프로토타입도 수정할 수 없습니다Object.freeze() 冻结一个对象,MDN的描述是 该方法冻结的对象不能被修改;即不能向这个对象添加新属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值,以及该对象的原型也不能被修改

export default {
 data: () => ({
   userList: []
}),
 async created() {
   const users = await axios.get("/api/users");
   this.userList = Object.freeze(users);
}
};

Vue2 的响应式源码地址:src/core/observer/index.js - 144行 是这样的

export function defineReactive (...){
   const property = Object.getOwnPropertyDescriptor(obj, key)
   if (property && property.configurable === false) {
       return
  }
   ...
}

可以看到一开始就判断 configurable 为 false 的直接返回不做响应式处理

configurable 为 false 表示这个属性是不能被修改的,而冻结的对象的 configurable 就是为 false

Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁

Vue3 里则是添加了响应式flag,用于标记目标对象类型

2. 虚拟滚动

如果是大数据很长的列表,全部渲染的话一次性创建太多 DOM 就会非常卡,这时就可以用虚拟滚动,只渲染少部分(含可视区域)区域的内容,然后滚动的时候,不断替换可视区域的内容,模拟出滚动的效果

<recycle-scroller
 class="items"
 :items="items"
 :item-size="24"
>
 <template v-slot="{ item }">
   <FetchItemView
     :item="item"
     @vote="voteItem(item)"
   />
 </template>
</recycle-scroller>

参考 vue-virtual-scroller、vue-virtual-scroll-list

原理是监听滚动事件,动态更新需要显示的 DOM,并计算出在视图中的位移,这也意味着在滚动过程需要实时计算,有一定成本,所以如果数据量不是很大的情况下,用普通的滚动就行

2. v-for 遍历避免同时使用 v-if

为什么要避免同时使用 v-for 和 v-if

在 Vue2 中 v-for 优先级更高,所以编译过程中会把列表元素全部遍历生成虚拟 DOM,再来通过 v-if 判断符合条件的才渲染,就会造成性能的浪费,因为我们希望的是不符合条件的虚拟 DOM都不要生成

在 Vue3 中 v-if 的优先级更高,就意味着当判断条件是 v-for 遍历的列表中的属性的话,v-if 是拿不到的

所以在一些需要同时用到的场景,就可以通过计算属性来过滤一下列表,如下

<template>
   <ul>
     <li v-for="item in activeList" :key="item.id">
      {{ item.title }}
     </li>
   </ul>
</template>
<script>
// Vue2.x
export default {
   computed: {
     activeList() {
       return this.list.filter( item => {
         return item.isActive
      })
    }
  }
}

// Vue3
import { computed } from "vue";
const activeList = computed(() => {
 return list.filter( item => {
   return item.isActive
})
})
</script>

3. 列表使用唯一 key

比如有一个列表,我们需要在中间插入一个元素,在不使用 key 或者使用 index 作为 key 会发生什么变化呢?先看个图

Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁

如图的 li1 和 li2 不会重新渲染,这个没有争议的。而 li3、li4、li5 都会重新渲染

因为在不使用 key 或者列表的 index 作为 key 的时候,每个元素对应的位置关系都是 index,上图中的结果直接导致我们插入的元素到后面的全部元素,对应的位置关系都发生了变更,所以在 patch 过程中会将它们全都执行更新操作,再重新渲染。

这可不是我们想要的,我们希望的是渲染添加的那一个元素,其他四个元素不做任何变更,也就不要重新渲染

而在使用唯一 key  的情况下,每个元素对应的位置关系就是 key,来看一下使用唯一 key

<template>
 <div>
   <div v-show="status" class="on">
     <my-components />
   </div>
   <section v-show="!status" class="off">
     <my-components >
   </section>
 </div>
</template>

Vue2의 반응형 소스 코드 주소: src/core/observer/index.js - line 144 이렇습니다🎜
<template functional>
 <div>
   <div class="content">{{ value }}</div>
 </div>
</template>
<script>
export default {
 props: [&#39;value&#39;]
}
</script>

// 或者
Vue.component(&#39;my-component&#39;, {
 functional: true, // 表示该组件为函数式组件
 props: { ... }, // 可选
 // 第二个参数为上下文,没有 this
 render: function (createElement, context) {
   // ...
}
})
🎜처음부터 볼 수 있듯이 configurablefalse이고 응답 처리 없이 직접 반환됩니다.🎜🎜configurablefalse 이는 이 속성을 수정할 수 없으며 고정된 개체의 configurablefalse🎜🎜Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁🎜🎜Vue3은 대상 개체 유형을 표시하기 위한 반응형 플래그를 추가합니다🎜

2. 가상 스크롤🎜🎜빅 데이터의 긴 목록이고 이를 모두 렌더링하는 경우 한 번에 너무 많은 DOM을 생성하면 매우 지연됩니다. 가상 스크롤을 사용하여 (가시 영역 포함) 영역 콘텐츠만 렌더링한 다음 스크롤할 때 가시 영역의 콘텐츠를 지속적으로 교체하여 스크롤 효과를 시뮬레이션할 수 있습니다🎜
<template>
 <div :style="{ opacity: number / 100 }">
   <div>{{ someThing() }}</div>
 </div>
</template>
<script>
export default {
 props:[&#39;number&#39;],
 methods: {
   someThing () { /* 耗时任务 */ }
}
}
</script>
🎜vue-virtual-scroller, vue를 참조하세요. -virtual-scroll-list🎜🎜원칙은 스크롤 이벤트를 모니터링하여 표시해야 하는 DOM을 동적으로 업데이트하고 뷰의 변위를 계산하는 것입니다. 이는 또한 스크롤 프로세스에 특정 비용이 있는 실시간 계산이 필요함을 의미합니다. , 따라서 데이터의 양이 그다지 크지 않으면 일반 스크롤을 사용하세요. 🎜

2. v-for 순회는 v-if를 동시에 사용하는 것을 방지합니다🎜🎜왜 v-forv-if🎜🎜Vue2에서는 <code>v-for의 우선순위가 더 높으므로 컴파일 프로세스에서는 모든 목록 요소를 탐색하여 가상 DOM을 생성한 다음 v-if를 사용하여 렌더링하기 전에 조건을 충족하는 요소를 결정합니다. 왜냐하면 가상 DOM이 수행되지 않기를 바라기 때문입니다. Vue3에서는 v-if가 더 높은 우선순위를 가지는데, 이는 판단 조건이 v-일 때 for가 탐색한 목록의 속성에 대해 v-if가 불가능함을 의미합니다. 얻을 수 있습니다🎜🎜따라서 동시에 사용해야 하는 일부 시나리오에서는 다음과 같이 속성을 계산하여 목록을 필터링할 수 있습니다🎜
<template>
<div>  
 <my-child />
</div>
</template>
<script>
export default {
components: {  
 MyChild: {  
  methods: {    
   someThing () { /* 耗时任务 */ }    
  },   
   render (h) {  
    return h(&#39;div&#39;, this.someThing())  
  } 
 }
}
}
</script>

3. 고유 키🎜🎜예를 들어 목록이 있고 중간에 요소를 삽입해야 하는 경우 키를 사용하지 않거나 인덱스를 키로 사용하면 어떻게 될까요? 먼저 사진부터 보시죠🎜🎜2 .png🎜🎜그림에 보이는 li1, li2는 다시 렌더링되지 않습니다. 이는 논란의 여지가 없습니다. 그리고 li3, li4, li5는 모두 다시 렌더링됩니다🎜🎜 왜냐하면 key 또는 목록의 index로 사용되지 않기 때문입니다. key 일 때, 각 요소의 해당 위치 관계는 인덱스입니다. 위 그림의 결과는 삽입한 요소부터 모든 후속 요소까지 해당 위치 관계가 직접 변경되므로 패치 중에 모두 실행됩니다. 프로세스를 업데이트하고 다시 렌더링합니다. 🎜🎜이것은 우리가 원하는 것이 아닙니다. 우리가 원하는 것은 다른 네 가지 요소를 변경하지 않고 추가된 요소를 렌더링하는 것이므로 다시 렌더링할 필요는 없지만 🎜🎜 키만 사용하세요. code> 이 경우, 각 요소에 해당하는 위치 관계는 <code>key 이며 고유한 key 값이 사용되는 경우를 살펴보겠습니다🎜

Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁

这样如图中的 li3 和 li4 就不会重新渲染,因为元素内容没发生改变,对应的位置关系也没有发生改变。

这也是为什么 v-for 必须要写 key,而且不建议开发中使用数组的 index 作为 key 的原因

4. 使用 v-show 复用 DOM

v-show:是渲染组件,然后改变组件的 display 为 block 或 none  v-if:是渲染或不渲染组件

所以对于可以频繁改变条件的场景,就使用 v-show 节省性能,特别是 DOM 结构越复杂收益越大

不过它也有劣势,就是 v-show 在一开始的时候,所有分支内部的组件都会渲染,对应的生命周期钩子函数都会执行,而 v-if 只会加载判断条件命中的组件,所以需要根据不同场景使用合适的指令

比如下面的用 v-show 复用DOM,比 v-if/v-else 效果好

<template>
 <div>
   <div v-show="status" class="on">
     <my-components />
   </div>
   <section v-show="!status" class="off">
     <my-components >
   </section>
 </div>
</template>

原理就是使用 v-if 当条件变化的时候,触发 diff 更新,发现新旧 vnode 不一致,就会移除整个旧的 vnode,再重新创建新的 vnode,然后创建新的 my-components 组件,又会经历组件自身初始化,renderpatch 等过程,而 v-show 在条件变化的时候,新旧 vnode 是一致的,就不会执行移除创建等一系列流程

5. 无状态的组件用函数式组件

对于一些纯展示,没有响应式数据,没有状态管理,也不用生命周期钩子函数的组件,我们就可以设置成函数式组件,提高渲染性能,因为会把它当成一个函数来处理,所以开销很低

原理是在 patch 过程中对于函数式组件的 render 生成的虚拟 DOM,不会有递归子组件初始化的过程,所以渲染开销会低很多

它可以接受 props,但是由于不会创建实例,所以内部不能使用 this.xx 获取组件属性,写法如下

<template functional>
 <div>
   <div class="content">{{ value }}</div>
 </div>
</template>
<script>
export default {
 props: [&#39;value&#39;]
}
</script>

// 或者
Vue.component(&#39;my-component&#39;, {
 functional: true, // 表示该组件为函数式组件
 props: { ... }, // 可选
 // 第二个参数为上下文,没有 this
 render: function (createElement, context) {
   // ...
}
})

6. 子组件分割

先看个例子

<template>
 <div :style="{ opacity: number / 100 }">
   <div>{{ someThing() }}</div>
 </div>
</template>
<script>
export default {
 props:[&#39;number&#39;],
 methods: {
   someThing () { /* 耗时任务 */ }
}
}
</script>

上面这样的代码中,每次父组件传过来的 number 发生变化时,每次都会重新渲染,并且重新执行 someThing 这个耗时任务

所以优化的话一个是用计算属性,因为计算属性自身有缓存计算结果的特性

第二个是拆分成子组件,因为 Vue 的更新是组件粒度的,虽然第次数据变化都会导致父组件的重新渲染,但是子组件却不会重新渲染,因为它的内部没有任何变化,耗时任务自然也就不会重新执行,因此性能更好,优化代码如下

<template>
<div>  
 <my-child />
</div>
</template>
<script>
export default {
components: {  
 MyChild: {  
  methods: {    
   someThing () { /* 耗时任务 */ }    
  },   
   render (h) {  
    return h(&#39;div&#39;, this.someThing())  
  } 
 }
}
}
</script>

7. 变量本地化

简单说就是把会多次引用的变量保存起来,因为每次访问 this.xx 的时候,由于是响应式对象,所以每次都会触发 getter,然后执行依赖收集的相关代码,如果使用变量次数越多,性能自然就越差

从需求上说在一个函数里一个变量执行一次依赖收集就够了,可是很多人习惯性的在项目中大量写 this.xx,而忽略了 this.xx 背后做的事,就会导致性能问题了

比如下面例子

<template> 
 <div :style="{ opacity: number / 100 }"> {{ result
}}</div>
</template>
<script>
import { someThing } from &#39;@/utils&#39;
export default {
 props: [&#39;number&#39;], 
 computed: {  
  base () { return 100 },  
  result () {   
   let base = this.base, number = this.number // 
保存起来    
  for (let i = 0; i < 1000; i++) {   
   number += someThing(base) // 避免频繁引用
this.xx   
  }   
   return number 
  }
}
}
</script>

8. 第三方插件按需引入

比如 Element-UI 这样的第三方组件库可以按需引入避免体积太大,特别是项目不大的情况下,更没有必要完整引入组件库

// main.js
import Element3 from "plugins/element3";
Vue.use(Element3)

// element3.js
// 完整引入
import element3 from "element3";
import "element3/lib/theme-chalk/index.css";

// 按需引入
// import "element3/lib/theme-chalk/button.css";
// ...
// import { 
// ElButton, 
// ElRow, 
// ElCol, 
// ElMain, 
// .....
// } from "element3";

export default function (app) { 
// 完整引入 
app.use(element3)  

// 按需引入 
// app.use(ElButton);
}

9. 路由懒加载

我们知道 Vue 是单页应用,所以如果没有用懒加载,就会导致进入首页时需要加载的内容过多,时间过长,就会出现长时间的白屏,很不利于用户体验,SEO 也不友好

所以可以去用懒加载将页面进行划分,需要的时候才加载对应的页面,以分担首页的加载压力,减少首页加载时间

没有用路由懒加载:

import Home from &#39;@/components/Home&#39;
const router = new VueRouter({  
 routes: [  
  { path: &#39;/home&#39;, component: Home }
]
})

用了路由懒加载:

const router = new VueRouter({
routes: [ 
 { path: &#39;/home&#39;, component: () => 
import(&#39;@/components/Home&#39;) }, 
 { path: &#39;/login&#39;, component: 
require(&#39;@/components/Home&#39;).default }
]
})

在进入这个路由的时候才会走对应的 component,然后运行 import 编译加载组件,可以理解为 Promise 的 resolve 机制

  • import:Es6语法规范、编译时调用、是解构过程、不支持变量函数等
  • require:AMD规范、运行时调用、是赋值过程,支持变量计算函数等

更多有关前端模块化的内容可以看我另一篇文章 前端模块化规范详细总结

10. keep-alive缓存页面

比如在表单输入页面进入下一步后,再返回上一步到表单页时要保留表单输入的内容、比如在列表页>详情页>列表页,这样来回跳转的场景等

我们都可以通过内置组件 <keep-alive></keep-alive> 来把组件缓存起来,在组件切换的时候不进行卸载,这样当再次返回的时候,就能从缓存中快速渲染,而不是重新渲染,以节省性能

只需要包裹想要缓存的组件即可

<template>
 <div id="app"> 
  <keep-alive>  
   <router-view/> 
  </keep-alive>
</div>
</template>
  • 也可以用 include/exclude 来 缓存/不缓存 指定组件
  • 可通过两个生命周期 activated/deactivated 来获取当前组件状态

11. 事件的销毁

Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件

而对于定时器addEventListener 注册的监听器等,就需要在组件销毁的生命周期钩子中手动销毁或解绑,以避免内存泄露

<script>
export default {  
 created() {   
  this.timer = setInterval(this.refresh, 2000)  
  addEventListener(&#39;touchmove&#39;, 
this.touchmove, false) 
 }, 
  beforeDestroy() {  
   clearInterval(this.timer)   
   this.timer = null   
   removeEventListener(&#39;touchmove&#39;, 
this.touchmove, false) 
 }
}
</script>

12. 图片懒加载

图片懒加载就是对于有很多图片的页面,为了提高页面加载速度,只加载可视区域内的图片,可视区域外的等到滚动到可视区域后再去加载

这个功能一些 UI 框架都有自带的,如果没有呢?

推荐一个第三方插件 vue-lazyload

npm i vue-lazyload -S

// main.js
import VueLazyload from &#39;vue-lazyload&#39;
Vue.use(VueLazyload)

// 接着就可以在页面中使用 v-lazy 懒加载图片了
<img  v-lazy="/static/images/Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁" alt="Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁" >

或者自己造轮子,手动封装一个自定义指令,这里封装好了一个兼容各浏览器的版本的,主要是判断浏览器支不支持 IntersectionObserver API,支持就用它实现懒加载,不支持就用监听 scroll 事件+节流的方式实现

const LazyLoad = { 
// install方法 
install(Vue, options) {  
 const defaultSrc = options.default 
 Vue.directive(&#39;lazy&#39;, {  
  bind(el, binding) {   
   LazyLoad.init(el, binding.value, defaultSrc) 
 },   
  inserted(el) {     
   if (IntersectionObserver) {    
    LazyLoad.observe(el)     
  } else {      
    LazyLoad.listenerScroll(el)    
  }  
 }, 
})
}, 
// 初始化 
init(el, val, def) { 
 el.setAttribute(&#39;data-src&#39;, val)  
 el.setAttribute(&#39;src&#39;, def)
}, 
// 利用IntersectionObserver监听el
observe(el) {  
 var io = new IntersectionObserver((entries) => {  
  const realSrc = el.dataset.src    
 if (entries[0].isIntersecting) {   
  if (realSrc) {      
   el.src = realSrc       
   el.removeAttribute(&#39;data-src&#39;)    
  }   
 } 
}) 
 io.observe(el)
}, 
// 监听scroll事件
listenerScroll(el) {  
 const handler =
LazyLoad.throttle(LazyLoad.load, 300) 
 LazyLoad.load(el)  
 window.addEventListener(&#39;scroll&#39;, () => {   
  handler(el) 
 })
}, 
// 加载真实图片 
load(el) {  
 const windowHeight =
document.documentElement.clientHeight
 const elTop = el.getBoundingClientRect().top  
 const elBtm = 
el.getBoundingClientRect().bottom 
 const realSrc = el.dataset.src  
 if (elTop - windowHeight < 0 && elBtm > 0) {  
  if (realSrc) {     
   el.src = realSrc    
   el.removeAttribute(&#39;data-src&#39;)  
  } 
 }
}, 
// 节流 
throttle(fn, delay) {  
 let timer  
 let prevTime  
 return function (...args) {   
  const currTime = Date.now() 
  const context = this    
  if (!prevTime) prevTime = currTime  
  clearTimeout(timer)   
  
  if (currTime - prevTime > delay) {     
   prevTime = currTime      
   fn.apply(context, args)    
   clearTimeout(timer)      
   return   
 }  

 timer = setTimeout(function () {   
  prevTime = Date.now()  
  timer = null   
  fn.apply(context, args)   
}, delay) 
}
},
}
export default LazyLoad

使用上是这样的,用 v-LazyLoad 代替 src

<img  v-LazyLoad="xxx.jpg" / alt="Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁" >

(学习视频分享:vuejs教程web前端

위 내용은 Vue 개발에서 성능을 최적화하는 방법은 무엇입니까? 공유할 수 있는 12가지 최적화 팁의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제