22 совета по оптимизации Vue, которые я использую в своем проекте

внешний интерфейс Vue.js
22 совета по оптимизации Vue, которые я использую в своем проекте

Код должен не просто запуститься, а просто сказать одну ерунду: кодовое слово найти не просто 👍, 🙇‍🙇‍🙇‍.

Демонстрационный код написан на Vue3 + ts + Vite, но также содержит советы по оптимизации, применимые к Vue2. Если оптимизация применима только к Vue3 или Vue2, я отмечу это в заголовке.

Оптимизация кода

Используйте ключ в v-для

использоватьv-forПри обновлении отображаемого списка элементов по умолчанию используется стратегия повторного использования на месте; при изменении данных списка она будет основываться наkeyзначение, чтобы определить, изменено ли значение, если изменено, повторно визуализировать этот элемент, в противном случае повторно использовать предыдущий элемент;

Примечания по использованию ключей:

  • Не используйте возможные дубликаты или возможные вариантыkeyзначение (консоль также выдаст напоминание)
  • Если данные в массиве имеют состояние, которое необходимо поддерживать (например, поле ввода), не используйте свойства массива.indexтак какkeyзначение, потому что если элемент вставлен или удален из массива, индекс элемента за ним изменится, что является неправильным состоянием привязки, когда Vue повторно используется на месте.
  • Если в массиве нет доступного уникального значения ключа, и массив не обновляется полностью, но при вставке или удалении данных с помощью push и splice, вы можете рассмотреть возможность добавления в него ключевого поля.Значение Symbol() может обеспечить уникальность .

Когда использовать какой ключ?

Это очень деликатный вопрос, в первую очередь нужно знать原地复用(наверное虚拟domизменить, два虚拟dom节点изkeyЕсли это то же самое, узел не будет воссоздан, но будет изменен исходный узел)

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

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

Таким образом, использование индекса в качестве ключа должно удовлетворять:

  1. Данные не имеют независимого состояния
  2. Данные не будут добавлены/удалены и другие операции, которые повлияют на ключевые изменения следующих элементов

Когда использовать id в качестве ключа?

для большинства данныхidединственный, этот несомненно одинkeyпредпочтительный ответ. Использование в большинстве случаевidтак какkeyне будет отображаться вышеbug. Но если вам нужно подумать о производительности, вам нужно подумать о том, следует ли вам использовать повторное использование на месте.

Это то же самое, что и отображение данных пейджинга выше, если вы используетеidтак какkey, можно предположить, что каждая часть данных на каждой страницеidне совпадают, поэтому при изменении страницы два虚拟DOM树узлаkeyсовершенно непоследовательно,vueИсходный узел удаляется и создается новый узел. Не исключено, что эффективность будет еще ниже. Но есть у него и свои преимущества. Толькоkeyможет помочьdiffОн более точно связывает для нас состояние, что особенно подходит для сценариев, в которых данные имеют независимые состояния, например данные списка с полями ввода или переключателями.

Итак, когда использоватьidтак какkey? Всего один момент:

  1. Недоступноindexтак какkeyкогда

Использовать ключ в v-if/v-else-if/v-else

Многие люди могут игнорировать этот пункт

Причина: по умолчанию Vue максимально эффективно обновляет DOM. Это означает, что когда он переключается между элементами одного и того же типа, он будет исправлять существующие элементы вместо того, чтобы удалять старый и добавлять новый в то же место. Неожиданные побочные эффекты могут возникнуть, если неидентичные элементы идентифицируются как идентичные.

Если есть только один v-if и нет v-else или v-if-else, ключ добавлять не нужно.

По сравнению с ключом v-for, ключ в v-if/v-else-if/v-else относительно прост, мы можем напрямую написать фиксированную строку или массив

  <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;
}

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

Не используйте v-for и v-if вместе (Vue2)

Этот метод оптимизации ограничен Vue2, приоритет v-for и v-if был скорректирован в Vue3.

Все это знают

никогда не ставьv-if а также v-forИспользуется на одном и том же элементе в одно и то же время.Привести кРуководство по стилю Vue2.x

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

Уведомление:в vue3v-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управляя DOMdisplayЗнакомство с CSS для управления отображением и скрытием элементов

Поскольку производительность операций добавления/удаления в DOM намного ниже, чем у свойств CSS, управляющих DOM

Поэтому, когда элемент нуждается в частых изменениях отображения/скрытия, мы используемv-showдля повышения производительности.

Когда элемент не нуждается в частых изменениях отображения/скрытия, мы передаемv-ifудаление DOM может сэкономить браузеру ресурсы, необходимые для рендеринга части DOM.

Используйте простые вычисляемые свойства

Сложные вычисляемые свойства должны быть разбиты на как можно больше простых свойств.

  • легко проверить

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

  • легко читать

    Упрощение вычисляемых свойств требует, чтобы вы давали каждому значению описательное имя, даже если оно не может использоваться повторно. Это позволяет другим разработчикам (и вам в будущем) сосредоточиться на коде, который им небезразличен, и понять, что происходит.

  • Лучше «Принять перемены»

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

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

Привести кРуководство по стилю Vue2

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

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

При изменении любого значения из поля ManufacturingCost, profitMargin, DiscountPercent будет пересчитана вся цена.

Но если мы изменим его на следующее

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

Если при изменении DiscountPercent будут пересчитаны только Discount и finalPrice, т.к.computedиз缓存特性, не будет пересчитывать basePrice

функциональные функциональные компоненты (Vue2)

Обратите внимание, что это только в качестве средства оптимизации в Vue2, 3.x, существует разница в производительности между сборкой, а функциональное состояние компонентов было значительно уменьшено, и в большинстве случаев применение незначительно. Поэтому на SFCSfunctionalМиграционный путь для разработчиков - это удалить этот атрибут и заменить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>
  • Нет этого (нет экземпляра)
  • нет ответных данных

Разделить компоненты

Какой? Написанный вами файл vue содержит более тысячи строк кода? 🤔

Разумное разделение компонентов может не только оптимизировать производительность, но и сделать код более читабельным. принцип единой функции

полученный изslides.com/AK Let yum/v UE from…

До оптимизации

<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 только сделает ваше приложение более медленным, потому что оно будет занимать много памяти в течение длительного времени.

уничтожение событий

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

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

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

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

Vue2 все еще может пройти$onceДобиться такого эффекта, конечно, можно и вoptionsAPIПЕРЕД УНИЧТОЖЕНИЕМ SINICR, но я рекомендую написать первый, потому что последний сделает код одной и той же функции более рассредоточенным.

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

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

Используйте подходящий тип изображения

Использовать веб-формат: Об этом и говорить нечего.Все мы знаем, что преимущества WebP заключаются в том, что он имеет лучший алгоритм сжатия данных изображения, который может привести к уменьшению размера изображения, и имеет качество изображения, неразличимое невооруженным глазом; качество изображения как без потерь, так и с потерями.Режим сжатия, альфа-прозрачность и характеристики анимации, эффект преобразования в JPEG и PNG достаточно превосходны, стабильны и однородны.

Используйте Interleaved GIF или Progressive JPEG: Еще один способ оптимизировать взаимодействие с пользователем — использовать изображения в формате GIF с чересстрочной разверткой или прогрессивные (Progressive Encoding) изображения JPEG. Прогрессивные файлы JPEG являются первымиразмыто, а потом постепенно стало понятно.

Разница между базовым JPEG и прогрессивным JPEG:

Есть два способа сохранить формат файла JPEG. Это базовый JPEG и прогрессивный JPEG.

Оба формата имеют одинаковый размер и данные изображения, и их расширение одинаково, единственная разница заключается в том, как они отображаются.

  • Baseline JPEG

Baseline JPEG.webp

  • Progressive JPEG

Progressive JPEG.webp

Прогрессивные преимущества JPEG:

  • Пользовательский опыт Файл jpeg, закодированный прогрессивным способом, рендеринг в браузере размыт до четкости. Пользователь может получить обратную связь о желаемой информации в градиентном изображении. Если содержимое не соответствует ожиданиям пользователя, пользователь может перейти на новую страницу раньше времени.
  • Размер файла Эксперименты показали, что при размере файла JPEG менее 10 КБ файл JPEG, использующий стандартную кодировку (оптимизирована таблица Хаффмана), меньше, чем файл JPEG, использующий кодировку градиента (вероятность появления 75%). Для файлов размером более 10 КБ файлы JPEG с градиентным кодированием с вероятностью 94% будут меньше, чем файлы со стандартным кодированием.

Сократите ненужные ответные данные

Всем известно, что отзывчивые данные в vue требуют дополнительной привязки к ним функций get и get обработки, если какие-то ваши данные не будут меняться или вы не хотите, чтобы их изменения вызывали какие-то побочные эффекты (обновление представлений или другое). Обычно я бы определил его так.

export default {
  data() {
    this.version = '10'; // 不会被做响应式处理
    return {
        /* ... */
    }
  }
}

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

В Vue3 это не может быть решено описанным выше методом, потому что гранулярность Proxy proxy — это весь объект, а не определенное свойство.

Используйте разумные алгоритмы обработки данных

Это относительно сравнивает силу структур данных и алгоритмов

Например, метод, преобразующий массив в многоуровневую структуру.

/**
 * 数组转树形结构,时间复杂度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()для импорта файла или зависимости, то файл или зависимость будут упакованы отдельно как небольшой продукт.路由懒加载а также异步组件Все используют этот принцип.

  • Отложенная загрузка маршрута
  • Асинхронные компоненты

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

Интернет

CDN:Первый — введение упомянутой выше CDN, локальная библиотека используется на этапе разработки, а конфигурация外部扩展(Externals)Исключите эти зависимости при упаковке. Затем импортируйте их через CDN в html файл

Пуш сервера:HTTP2 является относительно зрелым; после введения CDN выше мы можем использовать функцию HTTP2 Server Push для веб-сайтов, чтобы позволить браузерам загружать эти CDN и другие файлы заранее.

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

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

Оптимизация пользовательского опыта

Мы можем улучшить взаимодействие с пользователем до загрузки основного файла. Это может сократить время белого экрана.

Однако следует отметить, что существует много ресурсов, которые необходимо загрузить при первой загрузке страницы.Если ресурсы, связанные с загрузкой, размещены в DOM, ресурсы загрузки могут быть заблокированы другими ресурсами.

Поэтому рекомендуется, чтобы код css или js, связанный с загрузкой, был встроен в заголовок html, чтобы обеспечить загрузку соответствующих css и js при отображении загрузки. И не рекомендуется использовать высокопроизводительную или высокопроизводительную логику переваривания сети при загрузке, что впоследствии продлит время парсинга или загрузки других ресурсов.