**Computed properties** — это вычисляемые свойства, которые автоматически пересчитываются только тогда, когда изменились их реактивные зависимости.
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ name: 'Яблоко', price: 50, count: 3 },
{ name: 'Банан', price: 30, count: 5 },
{ name: 'Апельсин', price: 70, count: 2 },
])
// Computed автоматически пересчитается при изменении items
const totalPrice = computed(() => {
console.log('Пересчёт totalPrice...')
return items.value.reduce((sum, item) => sum + item.price * item.count, 0)
})
console.log(totalPrice.value) // 440
</script>
<template>
<p>Итого: {{ totalPrice }} руб.</p>
<!-- .value не нужен в шаблоне -->
</template>Computed **кэшируются**: результат вычисляется один раз и сохраняется. Повторные обращения возвращают кэшированное значение без вычислений.
Method вызывается **заново при каждом обращении**:
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
// Computed — вычислится один раз и закэшируется
const doubleComputed = computed(() => {
console.log('computed вычислился')
return count.value * 2
})
// Method — вычисляется при каждом вызове
function doubleMethod() {
console.log('method вызван')
return count.value * 2
}
</script>
<template>
<!-- Если вызвать несколько раз в шаблоне: -->
<p>{{ doubleComputed }}</p> <!-- computed вычислился — один раз -->
<p>{{ doubleComputed }}</p> <!-- из кэша, без повторного вычисления -->
<p>{{ doubleMethod() }}</p> <!-- method вызван -->
<p>{{ doubleMethod() }}</p> <!-- method вызван — снова! -->
</template>Кэширование особенно важно для дорогостоящих вычислений.
Computed не пересчитывается если зависимость не является реактивной:
// ПЛОХО — Date.now() не реактивен, computed никогда не обновится
const now = computed(() => Date.now())
// ХОРОШО — если нужно обновляемое время, используй watch + ref
const currentTime = ref(Date.now())
setInterval(() => { currentTime.value = Date.now() }, 1000)По умолчанию computed только для чтения. Но можно создать computed с геттером и сеттером:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Алексей')
const lastName = ref('Иванов')
const fullName = computed({
// Getter — вычисляет значение
get() {
return `${firstName.value} ${lastName.value}`
},
// Setter — позволяет "устанавливать" значение
set(value) {
const parts = value.split(' ')
firstName.value = parts[0]
lastName.value = parts[1] || ''
}
})
console.log(fullName.value) // 'Алексей Иванов'
fullName.value = 'Борис Петров' // вызовет setter
console.log(firstName.value) // 'Борис'
console.log(lastName.value) // 'Петров'
</script>| | Computed | Methods | Watch |
|---|---|---|---|
| Кэширование | Да | Нет | — |
| Возвращает значение | Да | Да | Нет |
| Реагирует на изменения | Да | — | Да |
| Побочные эффекты | Нельзя | Можно | Можно |
| Использование | Производные данные | Действия | Async-операции |
**Правило**: если нужно **получить производное значение** → computed. Если нужно **выполнить действие** при изменении → watch.
Система computed с dependency tracking — отслеживание зависимостей и автоматический пересчёт
// Реализуем упрощённую систему computed с трекингом зависимостей.
// В Vue эта система значительно сложнее, но принцип тот же.
// Стек активных вычислений (для отслеживания зависимостей)
const effectStack = []
// Создать отслеживаемое значение (аналог ref)
function signal(value) {
const subscribers = new Set()
return {
get() {
// Если есть активное вычисление — подписываем его
if (effectStack.length > 0) {
subscribers.add(effectStack[effectStack.length - 1])
}
return value
},
set(newValue) {
if (newValue !== value) {
value = newValue
console.log(`[signal] изменился -> ${newValue}, уведомляем ${subscribers.size} подписчиков`)
subscribers.forEach(fn => fn())
}
}
}
}
// Создать computed значение
function computed(getter) {
let cachedValue
let isDirty = true
const subscribers = new Set()
function recompute() {
isDirty = true
subscribers.forEach(fn => fn())
}
return {
get() {
if (effectStack.length > 0) {
subscribers.add(effectStack[effectStack.length - 1])
}
if (isDirty) {
// Запускаем геттер, отслеживая зависимости
effectStack.push(recompute)
cachedValue = getter()
effectStack.pop()
isDirty = false
console.log(`[computed] пересчитан -> ${cachedValue}`)
} else {
console.log(`[computed] из кэша -> ${cachedValue}`)
}
return cachedValue
}
}
}
// --- Демонстрация ---
const price = signal(100)
const quantity = signal(3)
const discount = signal(0)
// totalPrice зависит от price, quantity, discount
const totalPrice = computed(() => {
const subtotal = price.get() * quantity.get()
return subtotal - subtotal * (discount.get() / 100)
})
// discountedLabel зависит от totalPrice (цепочка computed!)
const label = computed(() => `Итого: ${totalPrice.get()} руб.`)
console.log('=== Первое чтение ===')
console.log(label.get())
console.log('\n=== Второе чтение (кэш) ===')
console.log(label.get())
console.log('\n=== Изменяем quantity ===')
quantity.set(5)
console.log(label.get()) // пересчёт cascades через зависимости
console.log('\n=== Изменяем discount ===')
discount.set(10)
console.log(label.get())**Computed properties** — это вычисляемые свойства, которые автоматически пересчитываются только тогда, когда изменились их реактивные зависимости.
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ name: 'Яблоко', price: 50, count: 3 },
{ name: 'Банан', price: 30, count: 5 },
{ name: 'Апельсин', price: 70, count: 2 },
])
// Computed автоматически пересчитается при изменении items
const totalPrice = computed(() => {
console.log('Пересчёт totalPrice...')
return items.value.reduce((sum, item) => sum + item.price * item.count, 0)
})
console.log(totalPrice.value) // 440
</script>
<template>
<p>Итого: {{ totalPrice }} руб.</p>
<!-- .value не нужен в шаблоне -->
</template>Computed **кэшируются**: результат вычисляется один раз и сохраняется. Повторные обращения возвращают кэшированное значение без вычислений.
Method вызывается **заново при каждом обращении**:
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
// Computed — вычислится один раз и закэшируется
const doubleComputed = computed(() => {
console.log('computed вычислился')
return count.value * 2
})
// Method — вычисляется при каждом вызове
function doubleMethod() {
console.log('method вызван')
return count.value * 2
}
</script>
<template>
<!-- Если вызвать несколько раз в шаблоне: -->
<p>{{ doubleComputed }}</p> <!-- computed вычислился — один раз -->
<p>{{ doubleComputed }}</p> <!-- из кэша, без повторного вычисления -->
<p>{{ doubleMethod() }}</p> <!-- method вызван -->
<p>{{ doubleMethod() }}</p> <!-- method вызван — снова! -->
</template>Кэширование особенно важно для дорогостоящих вычислений.
Computed не пересчитывается если зависимость не является реактивной:
// ПЛОХО — Date.now() не реактивен, computed никогда не обновится
const now = computed(() => Date.now())
// ХОРОШО — если нужно обновляемое время, используй watch + ref
const currentTime = ref(Date.now())
setInterval(() => { currentTime.value = Date.now() }, 1000)По умолчанию computed только для чтения. Но можно создать computed с геттером и сеттером:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Алексей')
const lastName = ref('Иванов')
const fullName = computed({
// Getter — вычисляет значение
get() {
return `${firstName.value} ${lastName.value}`
},
// Setter — позволяет "устанавливать" значение
set(value) {
const parts = value.split(' ')
firstName.value = parts[0]
lastName.value = parts[1] || ''
}
})
console.log(fullName.value) // 'Алексей Иванов'
fullName.value = 'Борис Петров' // вызовет setter
console.log(firstName.value) // 'Борис'
console.log(lastName.value) // 'Петров'
</script>| | Computed | Methods | Watch |
|---|---|---|---|
| Кэширование | Да | Нет | — |
| Возвращает значение | Да | Да | Нет |
| Реагирует на изменения | Да | — | Да |
| Побочные эффекты | Нельзя | Можно | Можно |
| Использование | Производные данные | Действия | Async-операции |
**Правило**: если нужно **получить производное значение** → computed. Если нужно **выполнить действие** при изменении → watch.
Система computed с dependency tracking — отслеживание зависимостей и автоматический пересчёт
// Реализуем упрощённую систему computed с трекингом зависимостей.
// В Vue эта система значительно сложнее, но принцип тот же.
// Стек активных вычислений (для отслеживания зависимостей)
const effectStack = []
// Создать отслеживаемое значение (аналог ref)
function signal(value) {
const subscribers = new Set()
return {
get() {
// Если есть активное вычисление — подписываем его
if (effectStack.length > 0) {
subscribers.add(effectStack[effectStack.length - 1])
}
return value
},
set(newValue) {
if (newValue !== value) {
value = newValue
console.log(`[signal] изменился -> ${newValue}, уведомляем ${subscribers.size} подписчиков`)
subscribers.forEach(fn => fn())
}
}
}
}
// Создать computed значение
function computed(getter) {
let cachedValue
let isDirty = true
const subscribers = new Set()
function recompute() {
isDirty = true
subscribers.forEach(fn => fn())
}
return {
get() {
if (effectStack.length > 0) {
subscribers.add(effectStack[effectStack.length - 1])
}
if (isDirty) {
// Запускаем геттер, отслеживая зависимости
effectStack.push(recompute)
cachedValue = getter()
effectStack.pop()
isDirty = false
console.log(`[computed] пересчитан -> ${cachedValue}`)
} else {
console.log(`[computed] из кэша -> ${cachedValue}`)
}
return cachedValue
}
}
}
// --- Демонстрация ---
const price = signal(100)
const quantity = signal(3)
const discount = signal(0)
// totalPrice зависит от price, quantity, discount
const totalPrice = computed(() => {
const subtotal = price.get() * quantity.get()
return subtotal - subtotal * (discount.get() / 100)
})
// discountedLabel зависит от totalPrice (цепочка computed!)
const label = computed(() => `Итого: ${totalPrice.get()} руб.`)
console.log('=== Первое чтение ===')
console.log(label.get())
console.log('\n=== Второе чтение (кэш) ===')
console.log(label.get())
console.log('\n=== Изменяем quantity ===')
quantity.set(5)
console.log(label.get()) // пересчёт cascades через зависимости
console.log('\n=== Изменяем discount ===')
discount.set(10)
console.log(label.get())Создай объект `store` с полями `firstName` и `lastName` (обычные строки), и добавь к нему computed-свойства через `Object.defineProperty`: `fullName` (= firstName + " " + lastName) и `greeting` (= "Привет, " + fullName). При изменении firstName или lastName соответствующие computed должны отражать новые значения при следующем обращении.
В Object.defineProperty передай третьим аргументом дескриптор { get() { return this.firstName + ' ' + this.lastName }, enumerable: true }. Внутри get используй this для обращения к store. Для greeting используй this.fullName чтобы не дублировать логику.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке