Если ты используешь Composition API, ты точно сталкивался с watch и watchEffect. Они похожи, но ведут себя по-разному — и неправильный выбор легко приводит к лишним рендерам, багам и «магии». Разберёмся, где какой инструмент уместен, с примерами из реальной разработки.
В чём разница на пальцах
Коротко:
watch— когда ты знаешь, за чем следитьwatchEffect— когда ты не хочешь думать о зависимостях
watch — контроль и предсказуемость
watch отслеживает конкретный источник (ref, computed, getter).
Пример
import { ref, watch } from 'vue'const search = ref('')
const results = ref([])watch(search, async (newValue, oldValue) => {
// вызывается только при изменении search
if (!newValue) {
results.value = []
return
} const res = await fetch(`/api?q=${newValue}`)
results.value = await res.json()
})
Что важно
- зависимость указываешь явно
- есть доступ к
oldValue - можно контролировать момент вызова (
immediate,flush)
watchEffect — реактивная магия
watchEffect автоматически собирает зависимости изнутри функции.
Пример
import { ref, watchEffect } from 'vue'const userId = ref(1)
const user = ref(null)watchEffect(async () => {
// Vue сам поймёт, что userId — зависимость
const res = await fetch(`/api/users/${userId.value}`)
user.value = await res.json()
})
Что важно
- зависимости определяются автоматически
- нет
oldValue - выполняется сразу при создании
Паттерн 1: API-запросы — почти всегда watch
Почему не watchEffect?
Потому что он может зацепить лишние зависимости.
watchEffect(() => {
console.log(userId.value) // если тут случайно появится ещё одна реактивная переменная —
// она тоже станет зависимостью
})
👉 В итоге запрос может триггериться неожиданно.
Правильный вариант
watch(userId, async (id) => {
const res = await fetch(`/api/users/${id}`)
user.value = await res.json()
})
Вывод:
API-запросы → watch
Паттерн 2: Синхронизация состояния — watchEffect
Когда тебе нужно просто «держать всё в актуальном состоянии».
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = ref('')watchEffect(() => {
fullName.value = `${firstName.value} ${lastName.value}`
})
👉 Тут не хочется перечислять зависимости.
Но! В реальности лучше использовать computed:
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
Паттерн 3: Работа с несколькими зависимостями
watch
watch([a, b], ([newA, newB]) => {
console.log(newA, newB)
})
watchEffect
watchEffect(() => {
console.log(a.value, b.value)
})
Когда что?
- если зависимостей немного и они фиксированы →
watch - если зависимостей много или они динамические →
watchEffect
Паттерн 4: Дебаг и читаемость
watch — легче дебажить
watch(userId, (newVal, oldVal) => {
console.log('changed from', oldVal, 'to', newVal)
})
watchEffect — сложнее понять триггер
watchEffect(() => {
console.log(userId.value)
console.log(settings.value)
})
👉 Какая переменная вызвала эффект? Неочевидно.
Паттерн 5: Побочные эффекты (side effects)
Оба подходят, но:
watch— для контролируемых эффектовwatchEffect— для быстрых реакций
Пример: сохранение в localStorage
watch(
() => form.value,
(val) => {
localStorage.setItem('form', JSON.stringify(val))
},
{ deep: true }
)
👉 Здесь важно контролировать изменения → watch
Подводные камни
1. watchEffect может вызвать лишние запросы
watchEffect(() => {
fetch(`/api/${userId.value}`) console.log(theme.value) // ← внезапная зависимость
})
👉 любое изменение theme → новый запрос
2. async внутри watchEffect
watchEffect(async () => {
const res = await fetch(`/api/${id.value}`)
})
⚠️ Vue отслеживает зависимости только до первого await
👉 после await реактивность может потеряться
3. очистка эффектов
watchEffect((onCleanup) => {
const controller = new AbortController() fetch('/api', { signal: controller.signal }) // отменяем предыдущий запрос
onCleanup(() => controller.abort())
})
👉 важно для debounce / race conditions
Когда что использовать — краткая таблица
| Сценарий | Использовать |
|---|---|
| API-запрос | watch |
| Явная зависимость | watch |
| Нужно oldValue | watch |
| Быстрая реактивная логика | watchEffect |
| Много зависимостей | watchEffect |
| Прототипирование | watchEffect |
TL;DR
watch— контроль, стабильность, продакшенwatchEffect— удобство, но с риском скрытых зависимостей- API-запросы и бизнес-логика → почти всегда
watch watchEffectхорошо подходит для быстрых реактивных эффектов и прототипов- если сомневаешься — бери
watch
Итог
watch и watchEffect — не взаимозаменяемые инструменты. Первый даёт контроль и предсказуемость, второй — скорость разработки. Основная ошибка — использовать watchEffect там, где важна точность зависимостей (например, API-запросы). В продакшене лучше быть чуть более явным и использовать watch. А watchEffect оставить для простых эффектов и случаев, где список зависимостей неочевиден.
