Vue

watch и watchEffect

Поле поиска должно отправлять запрос на сервер, когда пользователь меняет текст, но не на каждую букву и не дважды на одно значение. computed тут не подходит: он выводит значение, а нужен побочный эффект - сетевой запрос. Это работа для наблюдателя. Vue даёт два: watch следит за явно указанным источником, а watchEffect сам определяет зависимости по тому, что читает. Понимание разницы и тонкостей вроде очистки предыдущего запроса отделяет работающий поиск от мерцающего бага с гонками.

  • Поиск с задержкой: watch на текст запроса отправляет запрос к API при изменении и отменяет предыдущий незавершённый
  • Синхронизация состояния с localStorage: watch на значение сохраняет его в хранилище при каждом изменении
  • Загрузка данных при смене маршрута или выбранного элемента: watch на id перезапрашивает детали
  • Логирование и аналитика: watchEffect отправляет событие, когда меняется любое из читаемых им значений
  • Валидация поля в реальном времени: watch следит за вводом и обновляет сообщение об ошибке

Предварительные знания

  • Понимание ref и reactive из урока vue-06
  • Знание computed и разницы между значением и побочным эффектом из урока vue-07
  • JavaScript: асинхронность, промисы, async и await
  • Представление о жизненном цикле обновления компонента на уровне идеи

watch против watchEffect

Оба наблюдателя выполняют побочный эффект при изменении реактивного состояния, но отличаются способом задания зависимостей. watch требует явно указать источник: ref, функцию-геттер или массив источников. Его колбэк получает новое и старое значение, что удобно для сравнения. watchEffect не принимает источник: он сразу выполняет переданную функцию и автоматически подписывается на все реактивные значения, которые она прочитала.

АспектwatchwatchEffect
ИсточникУказывается явноОпределяется автоматически по чтению
Первый запускТолько при изменении (если нет immediate)Сразу при создании
Старое значениеДоступно в колбэкеНедоступно
Когда удобнееРеакция на конкретный источник, нужно старое значениеЭффект зависит от нескольких значений сразу

Выбор такой: watch берут, когда важно реагировать на конкретный источник или нужен доступ к старому значению - например, сравнить, что было и что стало. watchEffect удобнее, когда эффект читает несколько реактивных значений и перечислять их вручную утомительно. Цена автоматизма watchEffect в том, что он запускается сразу и иногда подхватывает зависимость, которую не ожидали.

watchEffect отслеживает только те значения, которые реально прочитаны при выполнении функции. Если значение читается внутри условия, которое в этот раз ложно, подписки на него не будет, пока ветка не выполнится. Это и сила (точность), и источник сюрпризов для тех, кто ждёт подписки на всё подряд.

Чем watchEffect отличается от watch в задании зависимостей?

Тайминг flush, deep и immediate

У наблюдателя есть опции, управляющие его поведением. Тайминг flush определяет, в какой момент относительно обновления DOM срабатывает колбэк. По умолчанию это pre: колбэк выполняется до того, как Vue обновит DOM. Значение post откладывает колбэк до после обновления DOM - это нужно, когда внутри требуется обратиться к уже обновлённому элементу. Значение sync запускает колбэк синхронно сразу при изменении, без пакетирования.

flushКогда срабатываетКогда выбирать
pre (по умолчанию)До обновления DOMОбычная реакция на изменение данных
postПосле обновления DOMНужен доступ к обновлённому DOM-элементу
syncСинхронно сразу при измененииРедко, когда важна мгновенная реакция без батчинга

Опция immediate заставляет watch выполнить колбэк сразу при создании, не дожидаясь первого изменения - удобно, когда начальную загрузку и реакцию на изменения хочется описать одним наблюдателем. Опция deep нужна для объектов: по умолчанию watch на reactive-объект реагирует на замену верхнего уровня, но не на изменение вложенных свойств. С deep true наблюдатель следит за всем деревом объекта рекурсивно.

Опция deep на большом объекте обходится дорого: Vue рекурсивно отслеживает каждое вложенное свойство при каждом изменении. На крупных структурах это заметная нагрузка. Если нужно следить за конкретным вложенным полем, лучше передать геттер на это поле, а не включать deep на всём объекте.

Когда нужно ставить flush со значением post?

Очистка эффектов и гонки запросов

Самая частая ошибка с наблюдателями это гонка асинхронных запросов. Пользователь печатает в поиске, watch на каждое изменение шлёт запрос. Запросы возвращаются в произвольном порядке, и ответ на старый текст может прийти позже ответа на новый, перезаписав актуальный результат устаревшим. Решение это очистка предыдущего эффекта перед запуском следующего.

Третий аргумент колбэка watch это функция onCleanup. В неё передают код, который Vue выполнит перед следующим запуском наблюдателя или при размонтировании компонента. В примере onCleanup отменяет предыдущий fetch через AbortController, поэтому устаревший ответ не перезапишет актуальный. В watchEffect та же функция onCleanup передаётся первым аргументом эффекта.

onCleanup решает не только гонки запросов. Через него отменяют таймеры setTimeout, отписываются от WebSocket-соединений и убирают слушатели событий, созданные внутри эффекта. Общий принцип: всё, что эффект создал в прошлый раз и что нужно отменить перед новым запуском, чистится через onCleanup.

Зачем нужна функция onCleanup в колбэке watch?

Связь с другими темами

Этот урок про побочные эффекты на изменение. Он завершает блок реактивности:

  • computed — Если нужно вывести значение без побочного эффекта, выбирают computed, а не watch
  • ref и reactive — Наблюдатели подписываются на изменения ref и reactive-источников

Итог

  • watch следит за явно указанным источником (ref, геттер или массив источников) и вызывает колбэк с новым и старым значением при изменении
  • watchEffect запускается сразу и сам отслеживает зависимости по тому, какие реактивные значения читает его функция, без явного списка источников
  • Тайминг flush определяет, когда срабатывает колбэк: pre до обновления DOM (по умолчанию), post после обновления DOM, sync синхронно сразу
  • Функция очистки через onCleanup отменяет предыдущий эффект перед новым запуском, что критично для отмены устаревших запросов и таймеров
  • Опция immediate запускает watch сразу при создании, опция deep заставляет следить за вложенными свойствами объекта

Связанные уроки

  • vue-07-computed — computed выводит значение без побочных эффектов, а watch предназначен именно для побочных эффектов на изменение
  • vue-06-ref-reactive — Наблюдатели следят за ref и reactive-источниками из предыдущего урока
watch и watchEffect

0

1

Войти