Vue.jsMiddleTechnical
В чём разница между вычисляемыми свойствами (computed properties) и watch'ерами? Когда использовать каждый?
computed — производное кэшированное значение для шаблона; watch — побочный эффект на изменение источника (fetch, навигация, логирование). Если нужно вычислить значение — computed; если нужно что-то сделать — watch.
Ключевое различие
computed отвечает на вопрос «какое значение я хочу видеть» — возвращает реактивный ref, кэшируется по зависимостям, пересчитывается только при их изменении. watch отвечает на вопрос «что нужно сделать, когда X изменился» — это эффект: fetch, навигация, логирование, синхронизация с внешним хранилищем.
Когда computed
- Значение зависит от других реактивных источников и нужно в шаблоне:
:disabled="isInvalid". - Нужна кэшированность — тяжёлая фильтрация, форматирование, агрегация списка.
- Нужна двусторонняя проекция:
computedсget/set(например, Date ↔ ISO-строка в форме).
Когда watch
- Надо запустить побочный эффект: fetch при смене параметра, push в router, отправить аналитику.
- Нужен доступ к старому значению (
(newVal, oldVal) => ...). - Нужна отложенная реакция после DOM-обновления:
flush: 'post'. - Нужна однократная реакция:
once: true(Vue 3.4+).
Пример
import { ref, computed, watch } from 'vue'
const query = ref('')
const items = ref<string[]>([])
// computed: чистое производное значение, кэшируется
const filtered = computed(() =>
items.value.filter(i => i.toLowerCase().includes(query.value.toLowerCase()))
)
// watch: побочный эффект при смене query
watch(query, async (newVal, oldVal) => {
if (newVal === oldVal) return
items.value = await searchApi(newVal)
}, { debounce: 300 })
watchEffect vs watch
import { watchEffect } from 'vue'
// watchEffect: автоматически отслеживает все зависимости внутри
watchEffect(async () => {
const result = await fetch(`/api/search?q=${query.value}`)
items.value = await result.json()
})
// Минус: нет oldVal, нет explicit source — зависимости неявные
Подводные камни
- Запросы к API в
computed— антипаттерн: каждый читатель триггерит эффект, кэширование ломается. computedленив: если его никто не читает, он не вычисляется. Не рассчитывайте, что он «сам сработает» какwatchEffect.- В
watchчасто нуженimmediate: true, если эффект должен выполниться при инициализации, а не только при первом изменении. flush: 'post'нужен, когда в callback хотите прочитать обновлённый DOM — без него DOM ещё не применён.- Мутировать реактивный объект изнутри
getcomputed — антипаттерн и источник бесконечных пересчётов. computedсget/setудобен для проекций (Date ↔ ISO-строка), но не превращайте его в стейт-машину с побочными эффектами.- При использовании
watchс объектом иdeep: true— производительность падает на больших деревьях; используйте точечный геттер вместо deep-наблюдения. - Не забывайте останавливать watcher'ы, созданные вне
setup: сохраните возвращаемыйunwatchи вызовите его вручную.
Common mistakes
- Делать сетевой запрос внутри
computed. - Использовать
watchтам, где хватитcomputed, и перегружать его логикой представления. - Забывать
immediate: true, когда эффект должен сработать при загрузке. - Менять источник внутри
watchбез guard и получать бесконечный цикл.
What the interviewer is testing
- Различает «получить значение» и «выполнить эффект».
- Понимает кэширование
computedпо зависимостям. - Знает опции
immediate,deep,flush,once. - Упоминает
computedс getter/setter как корректную двустороннюю проекцию.