Vue: знание того, когда использовать вычисляемые свойства, не повышает производительность?

внешний интерфейс Vue.js
Vue: знание того, когда использовать вычисляемые свойства, не повышает производительность?

Если вы пользователь Vue, вы знакомы с вычисляемыми свойствами, это очень удобно!

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

Чтобы было понятно, о чем мы говорим, когда говорим «вычисляемые свойства» в Vue, вот простой пример:

const todos = reactive([
  { title: 'Wahs Dishes', done: true},
  { title: 'Throw out trash', done: false }
])

const openTodos = computed(
  () => todos.filter(todo => !todo.done)
)

const hasOpenTodos = computed(
  () => !!openTodos.value.length
)

это здесь,openTodosзависит отtodos, а такжеhasOpenTodosзависит отopenTodos. Это удобно, потому что теперь у нас есть реактивные объекты, которые можно транспортировать и потреблять, а также автоматически обновлять при каждом изменении состояния, от которого они зависят.

Если мы используем эти реактивные объекты в реактивном контексте, таком как шаблон Vue, функция рендеринга илиwatch(), они также реагируют на изменения в вычисляемых свойствах и обновлениях — в конце концов, это магия в основе Vue.

Уведомление: я использую API композиции, потому что в последнее время часто использую его. Однако поведение, описанное в этой статье, также применимо к вычисляемым свойствам в обычном API параметров. В конце концов, оба используют одну и ту же реактивную систему.

1. Что особенного в вычисляемых свойствах

Есть две особенности вычисляемых свойств, которые делают их особенными, и они имеют отношение к теме этой статьи:

  1. Их результаты кэшируются, и их нужно пересчитывать только при изменении одной из их реактивных зависимостей.
  2. Они лениво оцениваются при доступе.

тайник

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

ленивая оценка

Вычисляемые свойства такжеИнерцияВычисления - но что это значит?

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

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

2. Когда ленивая оценка можетулучшатьвремя работы

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

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

<template>
  <input type="text" v-model="newTodo">
  <button type="button" v-on:click="addTodo">Save</button>
  <button @click="showList = !showList">
    Toggle ListView
  </button>
  <template v-if="showList">
    <template v-if="hasOpenTodos">
      <h2>{{ openTodos.length }} Todos:</h2> 
      <ul>
        <li v-for="todo in openTodos">
          {{ todo.title }}
        </li>
      </ul>
    </template>
    <span v-else>No todos yet. Add one!</span>
  </template>
</template>

<script setup>
const showListView = ref(false)

const todos = reactive([
  { title: 'Wahs Dishes', done: true},
  { title: 'Throw out trash', done: false }
])
const openTodos = computed(
  () => todos.filter(todo => !todo.done)
)
const hasOpenTodos = computed(
  () => !!openTodos.value.length
)

const newTodo = ref('')
function addTodo() {
  todos.push({
    title: todo.value,
    done: false
  })
}
</script>

См. этот код наSFC Playgroundзапускать на

из-заshowListизначально былfalse, функция шаблона/рендеринга не будет читатьopenTodos, так что фильтрации даже не происходит ни изначально, ни при добавлении нового todo иtodos.lengthпосле изменения. только вshowListустановить послеtrue, эти вычисляемые свойства считываются, и их вычисления запускаются.

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

3. Когда ленивая оценка будетуменьшатьвремя работы

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

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

Почему это проблема?

Другие части кода могут зависеть от этого вычисляемого свойства — возможно, другого вычисляемого свойства, возможно,watch(), возможно, функция шаблона/рендеринга.

Поэтому не выбирайте Vue, вы можете пометить только эти зависимости, так как обновления - «В случае» возвращаемое значение будет варьироваться.

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

проблема доказательства

Вот простой пример: допустим, у нас есть список элементов и кнопка для увеличения счетчика. Как только счетчик достигает 100, мы хотим отобразить список в обратном порядке (да, это глупый пример. Сделайте это).

(Вы можете найти это здесьSFC playgroundпоиграй с этим примером)\

<template>
  <button @click="increase">
    Click me
  </button>
  <br>
  <h3>
    List
  </h3>
  <ul>
    <li v-for="item in sortedList">
      {{ item }}
    </li>
  </ul>
</template>

<script setup>
import { ref, reactive, computed, onUpdated } from 'vue'

const list = reactive([1,2,3,4,5])

const count = ref(0)
function increase() {
  count.value++
}

const isOver100 = computed(() => count.value > 100)

const sortedList = computed(() => {
  // imagine this to be expensive
  return isOver100.value ? [...list].reverse() : [...list]
})

onUpdated(() => {
  // this eill log whenever the component re-renders
  console.log('component re-rendered!')
})
</script>

вопрос: Вы нажимаете кнопку 101 раз. Как часто наши компоненты перерисовываются?

Получил ответ? Уверен?

отвечать:он будет перерисовываться101 раз.

Я подозреваю, что некоторые из вас могут ожидать другого ответа, например: «один раз, на 101-м клике». Но это неправильно, и причина этого в ленивой оценке вычисляемых свойств.

Смущенный? Давайте проанализируем, что происходит, шаг за шагом:

  1. Когда мы нажимаем кнопку,countповысился. Компонент не перерисовывается, потому что мы не использовали счетчик в шаблоне.
  2. Но с тех порcountПосле изменения наше вычисляемое свойствоisOver100помечен как "грязный" - изменилась реактивная зависимость, поэтому ее возвращаемое значение необходимо пересчитать.
  3. Но из-за ленивых вычислений это будет прочитано только тогда, когда что-то ещеisOver100.valueслучается - до тех пор мы (и Vue) не знаем, вернет ли это вычисляемое свойство все ещеfalseили изменить наtrue.
  4. sortedListв зависимости отisOver100- так что он также должен быть помечен как «грязный». Опять же, он еще не будет пересчитан, так как это происходит только при чтении.
  5. Так как наш шаблон зависит отsortedList, и он помечен как «грязный» (возможно, он изменился и должен быть пересчитан), поэтому компонент будет перерисовываться.
  6. Во время рендеринга он читаетsortedList.value
  7. sortedListТеперь пересчитай и прочитайisOver100.value- теперь пересчитывает, но все жеfalseНазад снова.
  8. Итак, теперь мы повторно визуализируем компонента такжеПовторно запустить «жадный до производительности»sorteListВычисления, даже если во всем этом нет необходимости — результирующий новый виртуальный DOM/шаблон выглядит точно так же.

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

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

По сути такая комбинация:

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

4. Как решить эту проблему, если вы с ней столкнулись

Теперь у вас может возникнуть два вопроса:

  1. Ух ты! Это проблема?
  2. Как я могу избавиться от этого?

Итак, первое: успокойтесь. Обычно это не имеет большого значения. Реактивная система Vue, как правило, очень эффективна, как и повторный рендеринг, особенно сейчас в Vue 3. Часто некоторые ненужные обновления здесь и там все равно будут перерисовываться, чем по умолчанию.любое состояниеАналоги React работают лучшеизменить его по желанию.

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

Если это произойдет с вами, к счастью, у вас есть разные обходные пути:

  1. Используйте обычные функции вместо автономных вычисляемых свойств
  2. Используйте геттеры для объектов вместо вычисляемых свойств
  3. Используйте пользовательское свойство «с нетерпением вычисляемое»

Обычная функция

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

// computed 写法
const hasOpenTodos = computed(() => !!openTodos.value.length)
// usage
if (hasOpenTodos.value) {
  // list open todos
}

// 普通函数写法
const hasOpenTodos = () => !!openTodos.value.length
// Usage
if (hasOpenTodos()) {
  // list open todos
}

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

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

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

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

Getters

Я также видел, как это использовалось таким образом:

import { reactive, computed } from 'vue'
const state = reactive({
  name: 'Linusborg',
  bigName: computed(() => state.name.toUpperCase())
})

Это удобно, если вы хотите, чтобы некоторые свойства объекта были получены из других свойств.

Но на самом деле вычисляемое свойство в этом примере избыточно. Javascript имеет свой собственный способ получения состояния для свойств объекта, который называетсяGetters. В нем нет кэширования или ленивых вычислений, но он как раз здесь.

import { reactive } from 'vue'
const state = reactive({
  name: 'Linusborg',
  get bigName() { return state.name.toUpperCase() )
})

Проблема решена.

настроитьeagerComputedпомощник

Простые функции и геттеры хороши, но для тех из нас, кто привык к Vue, вычисляемые свойства могут показаться более удобными. К счастью, система реактивности Vue дает нам все инструменты, необходимые для создания собственной версииcomputed(), один для расчетажаждущий,Не делайтеленивыйСлучай.

давайте назовем этоeagerComputed()

import { watchEffect, shallowRef, readonly } from 'vue'
export function eagerComputed(fn) {
  const result = shallowRef()
  watchEffect(() => {
    result.value = fn()
  }, 
  {
    flush: 'sync' // needed so updates are immediate.
  })

  return readonly(result)
}

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

ПроверитьЭта игровая площадка SFCФиксированный пример на

когда ты используешьcomputed(), когда использоватьeagerComputed()?

  • computed()Используйте, когда вы выполняете сложные вычисления, которые на самом деле могут выиграть от кэширования и ленивых вычислений, и должны быть (повторно) вычислены только тогда, когда это действительно необходимо.
  • использоватьeagerComputed()Когда у вас есть простая операция, возвращаемое значение — обычно логическое значение — редко изменяется.

Уведомление: имейте в виду, что это все еще добавляет некоторые накладные расходы, поскольку использует кучу реактивных API - вОченьВ чувствительных сценариях простая функция обычно более эффективна.

Справочная статья:Dev.to/linususborg/v…

Спасибо, что потратили свое драгоценное время на чтение этой статьи, если эта статья дала вам небольшую помощь или вдохновение, пожалуйста, не скупитесь на лайки.[赞]и внимание[爱心], ваша поддержка является движущей силой для автора, чтобы продолжать творить.[比心]

Добро пожаловать, чтобы добавить меняWX, учиться вместе, ловить рыбу вместе~