12 советов по оптимизации производительности при разработке Vue, посмотрите, сколько вы использовали

внешний интерфейс JavaScript Vue.js
12 советов по оптимизации производительности при разработке Vue, посмотрите, сколько вы использовали

Эта статья участвует в "Боевой рекорд оптимизации производительности"Тема заявки на доклады

Оптимизация производительности — это проблема, с которой столкнется каждый разработчик.Особенно сейчас, когда все больше и больше внимания уделяется опыту, а конкуренция становится все более и более жесткой, для наших разработчиков это далеко не полная итерация и хорошая функция. далеко не достаточно.Самое главное сделать продукт хорошо,чтобы больше людей захотело его использовать,а пользователям было удобнее им пользоваться.Не в этом ли тоже воплощение ценности и способностей нашего разработчика. Уделение внимания проблемам с производительностью и оптимизация работы с продуктом гораздо ценнее, чем исправление нескольких безобидных ошибок.

В этой статье описаны некоторые из моих небольших навыков ежедневной разработки проектов Vue.Без лишних слов, давайте начнем.

1. Оптимизация производительности лонг-листа

1. Не реактивный

Например, списки участников, списки продуктов и т. д. — это чисто отображение данных.В сценариях, где не будет динамических изменений, нет необходимости реагировать на данные, что может значительно повысить скорость рендеринга.

такие как использование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

image.png

Vue3 добавляет отзывчивостиflag, используемый для обозначения типа целевого объекта, подробные сведения об отзывчивости Vue3 можно найти в другой моей статье.

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-для обхода избегайте одновременного использования v-if

Почему вам следует избегать использования обоихv-forа такжеv-if

в Vue2v-forПриоритет выше, поэтому в процессе компиляции будут пройдены все элементы списка для генерации виртуального DOM, а затем через v-if будут отрисованы только те, которые удовлетворяют условиям, что приведет к пустой трате производительности, т.к. мы надеемся, что виртуальный DOM, не соответствующий условиям, будет отрендерен.

в Vue3v-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. В списке используется уникальный ключ

Например, есть список, нам нужно вставить элемент в середину, что будет, если мы не используем ключ или используем индекс в качестве ключа? первый взгляд на картинку

diff3.jpg

как показаноli1а такжеli2Перерендеринга не будет, это не спорно. а такжеli3、li4、li5будет повторно отображать

потому что не пользуюсьkeyили списокindexтак какkeyКогда отношение положения каждого элемента является индексом, результат на приведенном выше рисунке напрямую приводит к тому, что элемент, который мы вставили во все следующие элементы, и соответствующее отношение положения изменилось, поэтому все они будут выполняться в процессе исправления. затем повторно визуализировать. Это не то, что мы хотим, мы хотим отрендерить добавленный элемент, а остальные четыре элемента не вносят никаких изменений, поэтому не перерисовываем

при использовании толькоkeyВ случае позиционное отношение, соответствующее каждому элементу, равноkey, взгляните на использование уникальныхkeyстоимость

diff4.jpg

Как на картинкеli3а такжеli4Он не будет перерисовываться, потому что содержимое элемента не изменилось, и соответствующее позиционное отношение не изменилось.

Вот почему v-for должен писать ключ, и не рекомендуется использовать индекс массива в качестве ключа в разработке.

4. Повторно используйте DOM с v-show

v-show: это визуализация компонента, а затем изменение отображения компонента на блокировку или отсутствие
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, а затем воссоздать новыйvnode, затем создайте новыйmy-componentsкомпонента, пройдет инициализацию самого компонента,render,patchи так далее, покаv-showКогда условия меняются, старые и новыеvnodeОн непротиворечив, и ряд процессов, таких как удаление и создание, выполняться не будут.

5. Используйте функциональные компоненты для компонентов без состояния

Для некоторого чистого отображения, без реагирующих данных, без управления состоянием и без компонентов функций привязки жизненного цикла, мы можем установить их как функциональные компоненты для повышения производительности рендеринга, поскольку они будут рассматриваться как функция, поэтому накладные расходы очень низкие.

Принципpatchпроцесс для функциональных компонентовrenderСгенерированный виртуальный DOM не будет иметь рекурсивного процесса инициализации подкомпонента, поэтому накладные расходы на рендеринг будут намного ниже.

это приемлемоprops, но не может использоваться внутри, так как экземпляр не будет созданthis.xxПолучить свойства компонента, записанные следующим образом

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

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

6. Сегментация подкомпонентов

Давайте посмотрим на пример

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

В приведенном выше коде каждый раз, когда передается родительский компонентnumberКаждый раз, когда происходит изменение, оно будет повторно отображаться и выполняться повторно.someThingэто трудоемкая задача

Таким образом, одна из оптимизаций заключается в использовании вычисляемых свойств, поскольку сами вычисляемые свойства имеют функцию кэширования вычисляемых результатов.

Второй — разделить на подкомпоненты, потому что обновление Vue — это компонентная гранулярность.Хотя первое изменение данных вызовет повторный рендеринг родительского компонента, подкомпонент не будет повторно рендериться, потому что изменений нет. в его внутренности, что занимает время. Естественно, задача не будет повторно выполняться, поэтому производительность лучше. Оптимизированный код выглядит следующим образом

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

7. Вариабельная локализация

Проще говоря, это сохранение переменных, на которые будут ссылаться несколько раз, потому что каждый доступthis.xx, так как это отзывчивый объект, он будет срабатывать каждый разgetter, а затем выполнить соответствующий код, который зависит от коллекции.Если вы используете переменную больше раз, производительность, естественно, ухудшится

По требованиям достаточно выполнить сбор зависимостей на переменную в функции, но многие по привычке пишут в проекте многоthis.xx, игнорируяthis.xxТо, что вы делаете за кулисами, вызовет проблемы с производительностью

Например следующий пример

<template>
  <div :style="{ opacity: number / 100 }"> {{ result }}</div>
</template>
<script>
import { someThing } from '@/utils'
export default {
  props: ['number'],
  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 '@/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Скомпилируйте и загрузите компоненты, которые можно понимать какPromiseизresolveмеханизм

  • import: Спецификация синтаксиса Es6, вызов во время компиляции, процесс деструктурирования, не поддерживает функции переменных и т. д.
  • require: Спецификация AMD, вызов во время выполнения, представляет собой процесс присваивания, поддерживает функции вычисления переменных и т. д.

Для получения дополнительной информации о модульности внешнего интерфейса см. мою другую статьюПодробное резюме модульной спецификации внешнего интерфейса

10. сохранить кэшированные страницы

Например, после ввода следующего шага на странице ввода формы и последующего возврата к предыдущему шагу на странице формы содержимое ввода формы должно быть сохранено, например, в列表页>详情页>列表页, такие сцены, которые прыгают туда-сюда и т.д.

Мы все можем использовать встроенные компоненты<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('touchmove', this.touchmove, false)
    },
    beforeDestroy() {
      clearInterval(this.timer)
      this.timer = null
      removeEventListener('touchmove', this.touchmove, false)
    }
}
</script>

12. Ленивая загрузка изображения

Ленивая загрузка изображений означает, что для страниц с большим количеством изображений для повышения скорости загрузки страницы загружаются только изображения в видимой области, а изображения за пределами видимой области загружаются после прокрутки до видимой области.

Эта функция поставляется с некоторыми фреймворками пользовательского интерфейса, если нет?

Порекомендуйте сторонний плагинvue-lazyload

npm i vue-lazyload -S

// main.js
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)

// 接着就可以在页面中使用 v-lazy 懒加载图片了
<img v-lazy="/static/images/1.png">

Или создайте свои собственные колеса и вручную инкапсулируйте пользовательскую инструкцию.Здесь инкапсулируется версия, совместимая с каждым браузером, в основном для того, чтобы определить, поддерживает ли браузер или нет.IntersectionObserverAPI, используйте его для реализации ленивой загрузки, если нет, используйте событие прокрутки мониторинга + дроссель для реализации

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('data-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('data-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 && elBtm > 0) {
      if (realSrc) {
        el.src = realSrc
        el.removeAttribute('data-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 v-LazyLoad="xxx.jpg" />

13. SSR

Я не практиковал это в проекте, поэтому не знаю, что делать.По оптимизации SSR можно прочитать эту статью:Подробное описание схемы оптимизации Vue-SSR, запишите сюда, сюда не переместится

Прекрасное прошлое

Эпилог

Если эта статья поможет вам немного, пожалуйста, поставьте лайк и поддержите ее, спасибо