Вычисляемые свойства
Простой пример
Выражения внутри шаблона удобны, но предназначены для простых операций. Большое количество логики в шаблоне сделает его раздутым и сложным для поддержки. Например, если есть объект с вложенным массивом:
js
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
И потребуется отображать разные сообщения, в зависимости от того, есть ли у author
какие-то книги или нет:
template
<p>Есть опубликованные книги:</p>
<span>{{ author.books.length > 0 ? 'Да' : 'Нет' }}</span>
В таком случае шаблон уже не будет простым и декларативным. Потребуется взглянуть на него, прежде чем понять, что он выполняет вычисления в зависимости от author.books
. Проблема усугубится, если подобные вычисления в шаблоне потребуются не один раз.
Поэтому для сложной логики, включающей реактивные данные, следует использовать вычисляемые свойства.
vue
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// ref вычисляемого свойства
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Да' : 'Нет'
})
</script>
<template>
<p>Есть опубликованные книги:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
В этом примере объявляем новое вычисляемое свойство publishedBooksMessage
. Функция computed()
ожидает передачи функции-геттера и возвращает значение в виде ref вычисляемого свойства. Подобно обычным ref-ссылкам, можно получить доступ к вычисленному результату через publishedBooksMessage.value
. Вычисляемые свойства в виде ref-ссылок также автоматически разворачиваются в шаблонах, поэтому можно ссылаться на них без .value
в выражениях шаблонов.
Вычисляемое свойство автоматически отслеживает свои реактивные зависимости. Vue знает, что publishedBooksMessage
зависит от значения author.books
, поэтому будет обновлять все привязки, которые зависят от publishedBooksMessage
, при изменениях author.books
.
См. также: Типизация вычисляемых свойств
Кэширование вычисляемых свойств vs. Методы
Можно заметить, что того же результата можно достичь с помощью метода в выражении:
template
<p>{{ calculateBooksMessage() }}</p>
js
// в компоненте
function calculateBooksMessage() {
return author.books.length > 0 ? 'Да' : 'Нет'
}
Вместо вычисляемого свойства можно объявить эту же функцию в качестве метода. Для отображаемого результата эти два подхода действительно одинаковы. Однако разница заключается в том, что вычисляемые свойства кэшируются на основе своих реактивных зависимостей. Вычисляемое свойство будет пересчитываться только при изменении одной из своих зависимостей. А значит, пока не изменится author.books
, любое число обращений к вычисляемому свойству publishedBooksMessage
будет немедленно возвращать ранее вычисленный результат, без необходимости повторного запуска функции-геттера.
Это также означает, что следующее вычисляемое свойство никогда не будет обновляться, потому что Date.now()
не является реактивной зависимостью:
js
// НЕ БУДЕТ РАБОТАТЬ
const now = computed(() => Date.now())
Для сравнения, вызов метода будет всегда запускать функцию, когда происходит перерисовка.
Зачем нужно кэширование? Представьте, что есть затратное вычисляемое свойство list
, которому требуется проходить по большому массиву и выполнять различные вычисления. Далее, могут быть другие вычисляемые свойства, которые зависят от значения list
. Без кэширования выполнять геттер list
потребуется во много раз больше, чем это нужно! Когда же необходимо обойтись без кэширования — стоит использовать метод.
Вычисляемое свойство с возможностью записи
Вычисляемые свойства по умолчанию состоят только из геттера. При попытке присвоить ему новое значение будет выброшено предупреждение во время выполнения. В редких случаях, когда требуется вычисляемое свойство «с возможностью записи», можно создать такое, указав и геттер и сеттер:
vue
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// геттер (для получения значения)
get() {
return firstName.value + ' ' + lastName.value
},
// сеттер (при присвоении нового значения)
set(newValue) {
// Примечание: это синтаксис деструктурирующего присваивания
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
Теперь, при выполнении fullName.value = 'John Doe'
вызовется сеттер вычисляемого свойства и значения firstName
и lastName
будут соответственно обновлены.
Getting the Previous Value
- Поддерживается только в версиях 3.4+
In case you need it, you can get the previous value returned by the computed property accessing the first argument of the getter:
vue
<script setup>
import { ref, computed } from 'vue'
const count = ref(2)
// This computed will return the value of count when it's less or equal to 3.
// When count is >=4, the last value that fulfilled our condition will be returned
// instead until count is less or equal to 3
const alwaysSmall = computed((previous) => {
if (count.value <= 3) {
return count.value;
}
return previous;
})
</script>
In case you're using a writable computed:
vue
<script setup>
import { ref, computed } from 'vue'
const count = ref(2)
const alwaysSmall = computed({
get(previous) {
if (count.value <= 3) {
return count.value;
}
return previous;
},
set(newValue) {
count.value = newValue * 2;
}
})
</script>
Лучшие практики
Геттеры должны быть без побочных эффектов
Важно помнить, что вычисляемые функции геттера должны быть чистыми функциями и не иметь побочных эффектов. Например, не делайте асинхронных запросов и не изменяйте DOM внутри геттера вычисляемого свойства! Думайте о вычисляемом свойстве как о декларативном описании того, как получить значение на основе других значений — его единственной обязанностью должно быть вычисление и возвращение этого значения. Далее в руководстве обсудим, как можно выполнять побочные эффекты в ответ на изменения состояния с помощью методов-наблюдателей watchers.
Избегайте мутации вычисляемого значения
Возвращаемое значение вычисляемого свойства это производное состояние. Считайте его временным снимком — при каждом изменении состояния источника создаётся новый снимок. Не имеет смысла изменять снимок, поэтому вычисляемое возвращаемое значение следует рассматривать как доступное только для чтения и никогда не изменять — вместо этого следует обновить состояние источника, от которого оно зависит, чтобы вызвать вычисление нового значения.