Введение и проблематика
В приложениях часто нужно вычислять производные данные:
Полное имя из имени и фамилии
Отфильтрованный список товаров
Итоговая сумма корзины
Можно использовать методы, но они пересчитываются при каждом рендере. Computed свойства решают эту проблему через кэширование.
computed — это реактивное кэшируемое значение. Оно пересчитывается только когда изменяются его зависимости.
Базовая теория
Создание computed
< template >
< p >Имя: {{ firstName }}</ p >
< p >Фамилия: {{ lastName }}</ p >
< p >Полное имя: {{ fullName }}</ p >
</ template >
< script setup >
import { ref , computed } from 'vue'
const firstName = ref ( 'Иван' )
const lastName = ref ( 'Иванов' )
// Вычисляемое свойство
const fullName = computed (() => {
return ` ${ firstName .value } ${ lastName .value } `
})
</ script >
Доступ к значению
< script setup >
import { ref , computed } from 'vue'
const count = ref ( 0 )
const doubled = computed (() => count .value * 2 )
// В скрипте — через .value
console .log ( doubled .value) // 0
count .value = 5
console .log ( doubled .value) // 10
</ script >
< template >
<!-- В шаблоне — автоматически -->
< p >{{ doubled }}</ p >
</ template >
Практические примеры
Фильтрация списка
< template >
< input v-model = "searchQuery" placeholder = "Поиск..." />
< ul >
< li v-for = "user in filteredUsers" :key = "user.id" >
{{ user.name }}
</ li >
</ ul >
</ template >
< script setup >
import { ref , computed } from 'vue'
const searchQuery = ref ( '' )
const users = ref ([
{ id : 1 , name : 'Иван Иванов' } ,
{ id : 2 , name : 'Пётр Петров' } ,
{ id : 3 , name : 'Мария Сидорова' }
])
const filteredUsers = computed (() => {
const query = searchQuery . value .toLowerCase ()
return users . value .filter (user =>
user . name .toLowerCase () .includes (query)
)
})
</ script >
Сумма корзины
< template >
< div v-for = "item in cart" :key = "item.id" >
{{ item.name }} — {{ item.price }} × {{ item.quantity }}
</ div >
< p >< strong >Итого: {{ totalPrice }} ₽</ strong ></ p >
</ template >
< script setup >
import { ref , computed } from 'vue'
const cart = ref ([
{ id : 1 , name : 'Товар A' , price : 100 , quantity : 2 } ,
{ id : 2 , name : 'Товар B' , price : 250 , quantity : 1 }
])
const totalPrice = computed (() => {
return cart . value .reduce ((sum , item) => {
return sum + item .price * item .quantity
} , 0 )
})
</ script >
computed vs методы
computed метод
< template >
< p >{{ reversedMessage }}</ p >
< p >{{ reversedMessage }}</ p >
< p >{{ reversedMessage }}</ p >
</ template >
< script setup >
import { ref , computed } from 'vue'
const message = ref ( 'Привет' )
// Вычисляется ОДИН раз, результат кэшируется
const reversedMessage = computed (() => {
console .log ( 'computed вызван' )
return message . value .split ( '' ) .reverse () .join ( '' )
})
</ script > В консоли: computed вызван — один раз
computed с геттером и сеттером
По умолчанию computed только для чтения. Но можно добавить сеттер:
< template >
< input v-model = "fullName" />
< p >Имя: {{ firstName }}</ p >
< p >Фамилия: {{ lastName }}</ p >
</ template >
< script setup >
import { ref , computed } from 'vue'
const firstName = ref ( 'Иван' )
const lastName = ref ( 'Иванов' )
const fullName = computed ({
get () {
return ` ${ firstName .value } ${ lastName .value } `
} ,
set (newValue) {
const parts = newValue .split ( ' ' )
firstName .value = parts[ 0 ] || ''
lastName .value = parts[ 1 ] || ''
}
})
</ script >
Пограничные кейсы
⚠️
computed должен быть чистой функцией — без побочных эффектов. Не изменяйте состояние внутри computed.
< script setup >
import { ref , computed } from 'vue'
const count = ref ( 0 )
const log = ref ([])
// ❌ Неправильно — побочный эффект
const doubled = computed (() => {
log . value .push ( 'вычислено' ) // Изменяем состояние!
return count .value * 2
})
// ✅ Правильно — чистая функция
const doubledCorrect = computed (() => {
return count .value * 2
})
</ script >
🚫
Не используйте асинхронные операции внутри computed. Для этого есть watch или watchEffect.
< script setup >
import { ref , computed , watch } from 'vue'
const userId = ref ( 1 )
// ❌ Неправильно — асинхронный код в computed
const user = computed ( async () => {
return await fetchUser ( userId .value)
})
// ✅ Правильно — используйте watch
const user = ref ( null )
watch (userId , async (newId) => {
user .value = await fetchUser (newId)
} , { immediate : true })
</ script >
Плюсы и минусы
Аспект computed метод Кэширование Да Нет Вызов в шаблоне {{ value }}{{ method() }}Аргументы Нет Да Для производных данных Идеально Избыточно Для действий Не подходит Идеально
Когда использовать
computed:
Производные данные (fullName из firstName + lastName)
Фильтрация и сортировка списков
Форматирование значений
Любые синхронные вычисления на основе реактивных данных
методы:
Обработчики событий
Операции с аргументами
Асинхронные операции
Действия с побочными эффектами
Вопросы интервьюера
Q: В чём разница между computed и методом?
computed кэшируется и пересчитывается только при изменении зависимостей. Метод вызывается каждый раз при обращении.
Q: Когда пересчитывается computed?
Только когда изменяются реактивные зависимости, использованные внутри функции computed.
Q: Можно ли записать значение в computed?
По умолчанию нет. Но можно создать computed с getter и setter для двусторонней привязки.
Q: Чем computed отличается от watch?
computed возвращает вычисленное значение. watch выполняет побочные эффекты при изменении данных.
Источники