>웹 프론트엔드 >View.js >12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다.

12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다.

WBOY
WBOY앞으로
2022-02-22 17:51:021873검색

이 글은 vue에 대한 관련 지식을 제공하며, 개발 시 12가지 성능 최적화 팁을 주로 소개합니다.

12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다.

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

이 기사에는 Vue 프로젝트의 일상적인 개발에 대한 몇 가지 팁이 기록되어 있습니다. 이제 시작하겠습니다.

1. 긴 목록 성능 최적화

1. 응답 없음

예를 들어 회원 목록, 제품 목록 등은 동적 변경이 없는 순수한 데이터 표시일 뿐입니다. 데이터의 반응형 처리는 렌더링 속도를 크게 향상시킬 수 있습니다. [관련 추천: vue.js 영상 튜토리얼]

예를 들어 객체를 고정하려면 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
    }
    ...}

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

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

12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다.

Vue3 里则是添加了响应式flag,用于标记目标对象类型,Vue3 的响应式详情可以看我另一篇文章

2. 虚拟滚动

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

<recycle-scroller>
  <template>
    <fetchitemview></fetchitemview>
  </template></recycle-scroller>

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

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

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

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

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

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

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

<template>
    <ul>
      <li>
        {{ item.title }}
      </li>
    </ul></template><script>// Vue2.xexport default {
    computed: {
      activeList() {
        return this.list.filter( item => {
          return item.isActive        })
      }
    }}// Vue3import { computed } from "vue";const activeList = computed(() => {
  return list.filter( item => {
    return item.isActive  })})</script>

3. 列表使用唯一 key

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

12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다.

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

因为在不使用 key 或者列表的 index 作为 key 的时候,每个元素对应的位置关系都是 index,上图中的结果直接导致我们插入的元素到后面的全部元素,对应的位置关系都发生了变更,所以在 patch 过程中会将它们全都执行更新操作,再重新渲染。这可不是我们想要的,我们希望的是渲染添加的那一个元素,其他四个元素不做任何变更,也就不要重新渲染

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

<template>
  <p>
    </p>
<p>
      <my-components></my-components>
    </p>
    <section>
      <my-components>
    </my-components></section>
  </template>
Vue2의 응답 소스 코드 주소: src/core/observer/index.js - line 144는 다음과 같습니다

<template>
  <p>
    </p>
<p>{{ value }}</p>
  </template><script>export default {
  props: [&#39;value&#39;]}</script>// 或者Vue.component('my-component', {
  functional: true, // 表示该组件为函数式组件
  props: { ... }, // 可选
  // 第二个参数为上下文,没有 this
  render: function (createElement, context) {
    // ...
  }})
12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다. 를 보면 알 수 있습니다 configurable은 처음부터 판단됩니다. false의 직접 반환은 응답 처리를 수행하지 않습니다🎜configurablefalse입니다. 이 속성은 수정할 수 없으며 고정된 개체구성 가능false🎜🎜12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다.🎜🎜Vue3은 대상 개체 유형을 표시하기 위한 반응형 플래그를 추가합니다. Vue3의 반응성에 대한 자세한 내용은 다음을 참조하세요. 내 다른 기사🎜🎜🎜 2. 가상 스크롤 🎜🎜🎜빅 데이터의 목록이 길면 DOM을 모두 렌더링하면 한 번에 너무 많은 DOM을 생성하기가 매우 지연됩니다. 이때 가상 스크롤을 사용할 수 있습니다. 콘텐츠의 작은 부분(표시 영역 포함)만 렌더링한 다음 스크롤할 때 표시 영역의 콘텐츠를 지속적으로 교체하여 스크롤 효과를 시뮬레이션합니다.🎜
<template>
  <p>
    </p>
<p>{{ someThing() }}</p>
  </template><script>export default {
  props:[&#39;number&#39;],
  methods: {
    someThing () { /* 耗时任务 */ }
  }}</script>
🎜vue-virtual-scroller, vue-virtual을 참조하세요. -scroll-list🎜🎜원칙은 스크롤 이벤트를 모니터링하고 DOM에 표시되어야 하는 내용을 동적으로 업데이트하고 뷰의 변위를 계산하는 것입니다. 이는 또한 스크롤 프로세스에 특정 비용이 있는 실시간 계산이 필요함을 의미합니다. 따라서 데이터의 양이 많지 않다면 그냥 일반 스크롤을 사용하세요🎜🎜2. v-for traversal v-if를 동시에 사용하지 마세요🎜🎜v-for를 사용하지 말아야 하는 이유 동시에 v-if🎜🎜Vue2에서는 v-for가 우선합니다. 레벨이 높으므로 컴파일 프로세스 중에 모든 목록 요소가 다음으로 이동됩니다. 가상 DOM을 생성한 후 렌더링하기 전에 조건 충족 여부를 확인하는 데 v-if를 사용하게 되는데, 이는 조건을 충족하지 않는 가상 DOM을 사용하지 않기를 바라기 때문에 성능 낭비가 발생합니다. 🎜🎜Vue3에서는 v-if의 우선순위가 더 높습니다. 즉, 판단 조건이 v-for가 순회하는 목록의 속성인 경우 v-if를 얻을 수 없다는 의미입니다🎜🎜 그래서 일부에서는 동시에 사용해야 하는 시나리오에서는 다음과 같이 계산된 속성을 통해 목록을 필터링할 수 있습니다🎜
<template>
  <p>
    <my-child></my-child>
  </p></template><script>export default {
  components: {
    MyChild: {
      methods: {
        someThing () { /* 耗时任务 */ }
      },
      render (h) {
        return h(&#39;p&#39;, this.someThing())
      }
    }
  }}</script>
🎜3. 목록은 고유 키를 사용합니다🎜🎜예를 들어 목록이 있는 경우 요소를 삽입해야 합니다. 중간에 키를 사용하지 않고 인덱스를 키로 사용하면 어떻게 될까요? 먼저 사진부터 보시죠🎜🎜12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다. 🎜🎜그림과 같이 li1li2는 다시 렌더링되지 않으므로 논란의 여지가 없습니다. 그리고 li3, li4, li5는 모두 다시 렌더링됩니다🎜🎜 왜냐하면 key 또는 목록의 index로 사용되지 않기 때문입니다. key 일 때, 각 요소의 해당 위치 관계는 인덱스입니다. 위 그림의 결과는 삽입한 요소부터 모든 후속 요소까지 해당 위치 관계가 직접 변경되므로 패치 중에 모두 실행됩니다. 프로세스를 업데이트하고 다시 렌더링합니다. 이것은 우리가 원하는 것이 아닙니다. 다른 네 가지 요소를 변경하지 않고 추가된 요소를 렌더링하므로 다시 렌더링할 필요가 없습니다🎜🎜그리고 고유한 를 사용할 때. , 각 요소의 해당 위치 관계는 key인데, 고유한 key 값을 사용하는 경우를 살펴보겠습니다🎜🎜🎜🎜

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

这也是为什么 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>
  <p>
    </p>
<p>
      <my-components></my-components>
    </p>
    <section>
      <my-components>
    </my-components></section>
  </template>

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

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

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

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

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

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

6. 子组件分割

先看个例子

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

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

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

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

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

7. 变量本地化

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

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

比如下面例子

<template>
  <p> {{ result }}</p></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.jsimport 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 '@/components/Home'const router = new VueRouter({
  routes: [
    { path: '/home', component: Home }
  ]})

用了路由懒加载:

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

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

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

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

10. keep-alive缓存页面

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

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

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

<template>
  <p>
    <keep-alive>
      <router-view></router-view>
    </keep-alive>
  </p></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.jsimport VueLazyload from 'vue-lazyload'Vue.use(VueLazyload)
// 接着就可以在页面中使用 v-lazy 懒加载图片了<img  alt="12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다." >

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

const LazyLoad = {
  // install方法
  install(Vue, options) {
    const defaultSrc = options.default
    Vue.directive('lazy', {
      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('src', val)
    el.setAttribute('src', 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('src')
        }
      }
    })
    io.observe(el)
  },
  // 监听scroll事件
  listenerScroll(el) {
    const handler = LazyLoad.throttle(LazyLoad.load, 300)
    LazyLoad.load(el)
    window.addEventListener('scroll', () => {
      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) {
      if (realSrc) {
        el.src = realSrc
        el.removeAttribute('src')
      }
    }
  },
  // 节流
  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  alt="12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다." >

13. SSR

这一点我在项目中也没有实践过,就不班门弄斧了,关于 SSR 的优化可以看这篇文章: Vue-SSR 优化方案详细总结,这里记录一下,就不搬过来了

(学习视频分享:web前端

위 내용은 12가지 팁! Vue 개발에서 성능을 최적화하도록 안내합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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