З Vue 3.4 обчислювана властивість запускає залежні ефекти (рендер, watch) лише якщо її значення справді змінилося, а не щоразу, коли змінилися її залежності.
const count = ref(1)
const isEven = computed(() => count.value % 2 === 0)
watchEffect(() => console.log(isEven.value))
count.value = 3 // isEven лишився false - ефект не запускається
count.value = 5 // теж
count.value = 6 // true - запускається
До 3.4 кожна зміна count змушувала перезапустити все, що читало isEven, навіть з тим самим результатом.
Порівняння - за Object.is. Для примітивів це працює чудово. А от computed, що щоразу повертає новий об'єкт чи масив, завжди вважається зміненим:
const activeItems = computed(() => items.value.filter((i) => i.active))
Будь-яка зміна items (навіть поля, яке не впливає на фільтр) дає новий масив - залежні компоненти оновлюються.
Ручна стабілізація - через попереднє значення, яке Vue передає першим аргументом:
const summary = computed((oldValue) => {
const next = { done: doneCount.value, total: total.value }
if (oldValue && oldValue.done === next.done && oldValue.total === next.total) {
return oldValue // те саме посилання - залежні не оновлюються
}
return next
})
Обов'язково обчислювати нове значення повністю до порівняння, щоб Vue зібрав усі залежності на кожному запуску.
Практичні висновки:
- computed, що повертає примітив (
boolean,number, рядок), - найкращий кандидат для передачі в дочірні компоненти й шаблони: стабільний автоматично; - для об'єктів, що рідко змінюються по суті, - стабілізація через
oldValue, коли вимірювання показують проблему; - побічних ефектів у computed бути не повинно: завдяки ліньому й кешованому обчисленню невідомо, скільки разів і коли він виконається.
Пов'язане: watch з deep: true на великих об'єктах теж дорогий - кожна зміна обходить усю структуру. З Vue 3.5 deep приймає число - глибину обходу.
Докладніше в документації: Продуктивність: стабільність computed