Что такое computed и watch? В каких случаях что лучше использовать?

MiddleVue.js #vue #reactivity

Вопрос

Что такое computed и watch во Vue и когда использовать один вместо другого?

Короткий ответ

computed — это производное, кэшируемое реактивное значение, которое пересчитывается только при изменении зависимостей и должно быть чистой функцией без побочных эффектов. watch — это побочный эффект, который выполняется в ответ на изменение реактивных данных (запрос к серверу, запись в localStorage, ручное изменение DOM).

Подробный ответ

  • computed(() => ...) возвращает реактивную ссылку, значение которой вычисляется из других реактивных данных. Vue кэширует результат и пересчитывает его только когда изменилась хотя бы одна из зависимостей, использованных внутри геттера — при повторных обращениях без изменений возвращается закэшированное значение. Используется, когда нужно получить новое значение, производное от текущего состояния (отфильтрованный список, сумма, форматированная строка).
  • watch(source, callback) запускает callback-функцию с побочным эффектом при изменении отслеживаемого источника. Не возвращает значение для использования в шаблоне — используется, когда на изменение данных нужно что-то сделать: сходить за данными на сервер, записать в localStorage, показать уведомление.

Практическое правило: если результат — это значение, которое нужно отобразить или использовать дальше — computed. Если результат — это действие (эффект) — watch.

Пример

<script setup>
import { ref, computed, watch } from 'vue'

const search = ref('')
const users = ref([{ name: 'Alice' }, { name: 'Bob' }])

// computed — производное значение, кэшируется, используется в шаблоне
const filteredUsers = computed(() =>
  users.value.filter(u => u.name.toLowerCase().includes(search.value.toLowerCase()))
)

// watch — побочный эффект при изменении search
watch(search, (newValue) => {
  fetch(`/api/log-search?q=${newValue}`)
})
</script>

<template>
  <input v-model="search" />
  <ul>
    <li v-for="user in filteredUsers" :key="user.name">{{ user.name }}</li>
  </ul>
</template>

Дополнительные вопросы

  • Почему computed не должен содержать побочные эффекты (например, запросы к API)?
  • Чем watch отличается от watchEffect?
  • Что произойдёт, если внутри computed вызвать Date.now() или Math.random()?