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 не принимает источник: он сразу выполняет переданную функцию и автоматически подписывается на все реактивные значения, которые она прочитала.
| Аспект | watch | watchEffect |
|---|---|---|
| Источник | Указывается явно | Определяется автоматически по чтению |
| Первый запуск | Только при изменении (если нет 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-источниками из предыдущего урока