Каждый, кто использует Vue для разработки или только его изучает, так или иначе встречается с необходимостью выполнить какое‑либо побочное действие при изменении значений, и сразу в голове возникает мысль о двух методах‑наблюдателях — Watch и WatchEffect.

Как работают эти 2 метода можно узнать из великолепной документации Vue, а в этой статье мы посмотрим на примеры самых часто используемых компонентов вместе с наблюдателями — по 2 компонента на каждый метод — а заодно вы сможете больше понять принцип их работы.

По ходу статьи также будут приведены некоторые полезные библиотеки Vue, которые часто используются в разработке.

Watch

Поисковая строка

Начнем с более простого примера.

Первым делом давайте создадим простой компонент, который будет содержать поле для ввода (обычный инпут) и с помощью v-model привяжем к нему реактивную переменную:

<script setup>
import { ref } from 'vue';

const search = ref('');
</script>

<template>
  <input v-model="search" type="text" placeholder="Поиск по сайту">
</template>

Если вы не знакомы с работой v-model, то об этом можете почитать в документации.

Теперь самое интересное — это логика работы поиска: при изменении строки поиска будет выполняться запрос к серверу с соответствующим значением. В качестве сервера с данными будем использовать JSONPlaceholder с адресом сервера, где search — поисковой запрос:

https://jsonplaceholder.typicode.com/posts?q={search}

При вводе символов в поисковую строку необходимо создавать запрос на сервер и сохранять данные в реактивное значение для их последующего отображения.

Напишем в скрипте функцию для запроса данных fetchPosts(val), которая состоит из одного параметра, в который будет попадать обновленное значение поиска:

const fetchPosts = async (val) => {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/posts?q=${val}`);

    if (!response.ok) {
      throw new Error('Что-то пошло не так');
    }

    const data = await response.json();

    return data;
  } catch (e) {
    console.error(e);
    return [];
  }
};

Будем вызывать эту функцию каждый раз, когда мы вводим новое значение и выводить полученные данные в консоль. Здесь нам и нужен наблюдатель за значением поиска для выполнения побочного действия. Используем метод Watch, т.к. отслеживаем всего одно значение. Первым аргументом будет отслеживаемое значение (без обращения к search.value, Vue сделает это за нас), а вторым аргументом выступает колбэк‑функция, первым параметром которой будет обновленное значение поиска:

watch(search, async (newVal) => {
  const data = await fetchPosts(newVal);
  console.log(data);
});

Поиск готов, при каждом изменении символа в строке поиска вызывается функция fetchPosts(), а затем данные выводятся в консоль.

Необходимо также позаботиться о том, чтобы не выполнять запросы через каждый введенный символ, а только тогда, когда пользователь закончит печатать. В ином случае будет отсылаться очень много запросов на сервер, что может ухудшить его производительность.

Для этого используем отложенный наблюдатель WatchDebounced вместо обычного Watch, который содержит библиотека утилит VueUse. Эта библиотека используется в большинстве проектов Vue. Достаточно настроить время задержки:

watchDebounced(search, async (newVal) => {
  const data = await fetchPosts(newVal);
  console.log(data);
}, { debounce: 800, maxWait: 1600 });

Теперь при длинном значении поиска вместо запроса на каждый изменённый символ, как на предыдущем изображении, мы обойдемся меньшим количеством запросов на итоговое значение поиска и намного сократим число запросов к серверу:

Как можете заметить количество запросов поиска сократилось практически до 1 вместо 34. Очень заметная оптимизация. Двигаемся к следующему примеру.

Валидация форм

Наблюдатели также часто используются при валидации форм без использования сторонних библиотек.

Составим шаблон простой формы для подписки на рассылку, состоящей из 3-х полей: имя, почта и чекбокс о согласии с политикой обработки ПД:

<template>
  <form>
    <label for="name">Имя</label>
    <input type="text" id="name" name="name" placeholder="Введите имя">
    <span>Текст ошибки</span>

    <label for="email">Email</label>
    <input type="email" id="email" name="email" placeholder="example@gmail.com">
    <span>Текст ошибки</span>

    <input type="checkbox" id="agreement" name="agreement">
    <label for="agreement">Согласен с политикой обрабоки <a href="#">персональных данных</a></label>
    <span>Текст ошибки</span>

    <button type="submit">Отправить</button>
  </form>
</template>

Создадим реактивный объект, содержащий значения всех полей и сообщений об ошибках:

<script setup>
import { reactive } from 'vue';

const fields = reactive({
  name: { value: '', error: '' },
  email: { value: '', error: '' },
  agreement: { value: false, error: '' }
});

</script>

Не забудем также привязать поля формы к соответствующим полям реактивного объекта с помощью v-model и распределить их ошибки:

<template>
  <form @submit.prevent="handleSubmit">
    <label for="name">Имя</label>
    <input v-model="fields.name.value" type="text" id="name" name="name" placeholder="Введите имя">
    <span v-if="fields.name.error">{{ fields.name.error }}</span>

    <label for="email">Email</label>
    <input v-model="fields.email.value" type="email" id="email" name="email" placeholder="example@gmail.com">
    <span v-if="fields.email.error">{{ fields.email.error }}</span>

    <input v-model="fields.agreement.value" type="checkbox" id="agreement" name="agreement">
    <label for="agreement">Согласен с политикой обрабоки <a href="#">персональных данных</a></label>
    <span v-if="fields.agreement.error">{{ fields.agreement.error }}</span>

    <button type="submit">Отправить</button>
  </form>
</template>

Перед тем, как перейти к разработке валидации, создадим базовую функцию проверки ошибок и отправки данных формы. С помощью computed создадим вычисляемое значение hasErrors, в котором проходит проверка всех значений объекта fields на наличие ошибок:

const hasErrors = computed(() => {
  return Object.values(fields).some(field => field.error);
});

И напишем обработчик отправки формы, который просто выводит в консоль сообщение об успешной отправке, если нет ни одной ошибки формы, а затем сбрасывает все значения формы до исходного состояния:

const handleSubmit = async () => {
  if (hasErrors.value) return;

  try {
    console.log('Форма отправлена:', {
      name: fields.name.value,
      email: fields.email.value,
      agreement: fields.agreement.value
    });

    fields.name = { value: '', error: '' };
    fields.email = { value: '', error: '' };
    fields.agreement = { value: false, error: '' }
  } catch (error) {
    console.error('Ошибка отправки формы:', error);
  }
};

Теперь самое интересное. Нужно наблюдать за каждым полем формы и при его изменении проводить валидацию. Но есть один нюанс – Watch не умеет отслеживать вложенные поля объектов, которые передаются напрямую. В этом случае следует применить функцию-getter, которая вернет необходимое значение. Посмотрим на пример валидации поля имени:

// ✅ Правильный способ – используется функция-getter
watch(() => fields.name.value, (newName) => {
  fields.name.error = newName.trim() ? '' : 'Обязательное поле';
});

// ❌ Такой способ не сработает. Vue не сможет отследить переданное значение
watch(fields.name.value, (newName) => {
  fields.name.error = newName.trim() ? '' : 'Обязательное поле';
});

Выше мы также следим за новым значением имени и у реактивного объекта fields устанавливаем соответствующее сообщение об ошибке.

Похожим образом добавим валидацию остальных полей:

// Валидация email
watch(() => fields.email.value, (newEmail) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  fields.email.error = !newEmail.trim()
    ? 'Обязательное поле'
    : !emailRegex.test(newEmail) ? 'Некорректный email' : '';
});

// Валидация соглашения
watch(() => fields.agreement.value, (newAgreement) => {
  fields.agreement.error = newAgreement ? '' : 'Требуется согласие';
});

Последний момент, который стоит учесть, это проблема при отправке формы, как только компонент формы появился на странице. Дело в том, что Watch не вызывается при создании компонента, и форма будет отправлена даже с неправильными значениями, т.к. ошибок полей по умолчанию нет.

Есть 2 возможных решения. Первый способ заключается в передаче третьего аргумента с параметрами в наблюдатель. Чтобы данный наблюдатели сработали в момент появления компонента, используется опция immediate со значение true:

watch(() => fields.name.value, (newName) => {
  ...
}, { immediate: true });

// ----

watch(() => fields.email.value, (newEmail) => {
  ...
}, { immediate: true });

// ----

watch(() => fields.agreement.value, (newAgreement) => {
  ...
}, { immediate: true });

Теперь форма валидируется сразу, как только пользователь ее увидит. Но такой способ выглядит нелогично, ведь ошибки отображаются всегда. Поэтому воспользуемся следующим способом, а этот останется только в качестве демонстрации.

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

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

const fields = reactive({
  name: { value: '', error: '' },
  email: { value: '', error: '' },
  agreement: { value: false, error: '' }
});

const hasErrors = computed(() => {
  return Object.values(fields).some(field => field.error);
});

const validateName = (field) => {
  fields.name.error = field.trim() ? '' : 'Обязательное поле';
}

const validateEmail = (field) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  fields.email.error = !field.trim()
    ? 'Обязательное поле'
    : !emailRegex.test(field)
      ? 'Некорректный email'
      : '';
}

const validateAgreement = (field) => {
  fields.agreement.error = field ? '' : 'Требуется согласие';
}

const validateAll = () => {
  validateName(fields.name.value);
  validateEmail(fields.email.value);
  validateAgreement(fields.agreement.value);
}

const handleSubmit = async () => {
  validateAll();

  if (hasErrors.value) return;

  try {
    console.log('Форма отправлена:', {
      name: fields.name.value,
      email: fields.email.value,
      agreement: fields.agreement.value
    });

    fields.name = { value: '', error: '' };
    fields.email = { value: '', error: '' };
    fields.agreement = { value: false, error: '' }
  } catch (error) {
    console.error('Ошибка отправки формы:', error);
  }
};

// Валидация имени
watch(() => fields.name.value, (newName) => {
  validateName(newName);
});

// Валидация email
watch(() => fields.email.value, (newEmail) => {
  validateEmail(newEmail);
});

// Валидация соглашения
watch(() => fields.agreement.value, (newAgreement) => {
  validateAgreement(newAgreement);
});

</script>

<template>
  <form @submit.prevent="handleSubmit">
    <label for="name">Имя</label>
    <input v-model="fields.name.value" type="text" id="name" name="name" placeholder="Введите имя">
    <span v-if="fields.name.error">{{ fields.name.error }}</span>

    <label for="email">Email</label>
    <input v-model="fields.email.value" type="email" id="email" name="email" placeholder="example@gmail.com">
    <span v-if="fields.email.error">{{ fields.email.error }}</span>

    <input v-model="fields.agreement.value" type="checkbox" id="agreement" name="agreement">
    <label for="agreement">Согласен с политикой обрабоки <a href="#">персональных данных</a></label>
    <span v-if="fields.agreement.error">{{ fields.agreement.error }}</span>

    <button type="submit">Отправить</button>
  </form>
</template>

Таким простым образом мы можем подключить живую валидацию формы без использования сторонних библиотек.

Также существуют очень полезные библиотеки для валидации форм. Их стоит использовать для форм с более сложной логикой и большим количеством полей. С ними стоит обязательно познакомиться, т.к. в проектах они используются достаточно часто: VeeValidate и, если используете TypeScript, ZOD схемы. Обе библиотеки отлично работают в связке друг с другом.

WatchEffect

Наблюдатель WatchEffect очень похож на Watch, но с рядом отличий:

  • Не принимает аргумент с отслеживаемой переменной. Вместо этого, он отслеживает изменения всех реактивных значений, которые читаются внутри функции-колбека этого наблюдателя. То есть чаще всего этот метод используется для наблюдения за несколькими значениями одновременно.

  • В отличие от Watch, WatchEffect срабатывает сразу при создании компонента и это поведение нельзя изменить

Если попытаться привести Watch к такому же поведению, что и WatchEffect, то получится следующее:

// Не задаем параметров и не передаем отслеживаемое значение
watchEffect(() => {
  ...
});

// Передаем отслеживаемое значение первым аргументом, а также объект
// с параметрами
watch(reactiveValue, () => {
  ...
}, { deep: true, immediate: true })

Для того, чтобы наглядно показать разницу и возможности WatchEffect, посмотрим на предыдущий пример с реализацией валидации форм.

Валидация форм

В прошлом примере мы использовали три раздельных наблюдателя за каждым полем. С помощью WatchEffect мы можем легко объединить их.

Вместо такого кода:

// Валидация имени
watch(() => fields.name.value, (newName) => {
  validateName(newName);
});

// Валидация email
watch(() => fields.email.value, (newEmail) => {
  validateEmail(newEmail);
});

// Валидация соглашения
watch(() => fields.agreement.value, (newAgreement) => {
  validateAgreement(newAgreement);
});

Получаем следующий:

watchEffect(() => {
  validateName(fields.name.value);
  validateEmail(fields.email.value);
  validateAgreement(fields.agreement.value);
});

Код получился простым, но теперь при изменении любого поля проходит валидация сразу всей формы, а также валидация срабатывает при создании компонента из-за особенностей WatchEffect.

Исправим проблему с валидацией всей формы при редактировании одного значения. Для этого заведем обычную переменную за пределами наблюдателя, которая будет хранить предыдущие значения формы:

const prevValues = {
  name: '',
  email: '',
  agreement: false,
};

Затем добавим в наблюдатель проверку текущих значений формы с предыдущими. Если есть разница, значит было изменено определенное поле и именно у него выполняем валидацию. В конце не забудем поменять данные предыдущих значений:

watchEffect(() => {
  if (fields.name.value !== prevValues.name) validateName(fields.name.value);
  if (fields.email.value !== prevValues.email) validateEmail(fields.email.value);
  if (fields.agreement.value !== prevValues.agreement) validateAgreement(fields.agreement.value);

  prevValues.name = fields.name.value;
  prevValues.email = fields.email.value;
  prevValues.agreement = fields.agreement.value;
});

Таким способом мы убили сразу двух зайцев: валидируется только определенное поле при изменении, а также валидация не производится при создании компонента.

Код выше срабатывает из-за того, что мы явно прочитали реактивные значения объекта fields

Перейдем к последнему компоненту.

Фильтрация товаров

Компонент связан с множественным выборов параметров, которые будут влиять на отображение товаров. Ниже приведен готовый компонент с данными о товарах и логикой их отображения:

<script setup>
import { ref } from 'vue'

// Исходный список товаров
const items = ref([
  { id: 1, name: 'Ноутбук', category: 'Электроника', price: 80000 },
  { id: 2, name: 'Кроссовки', category: 'Одежда', price: 6000 },
  { id: 3, name: 'Смартфон', category: 'Электроника', price: 50000 },
  { id: 4, name: 'Книга', category: 'Книги', price: 800 },
  { id: 5, name: 'Футболка', category: 'Одежда', price: 1200 },
  { id: 6, name: 'Холодильник', category: 'Бытовая техника', price: 45000 },
  { id: 7, name: 'Сковорода', category: 'Кухня', price: 2500 },
  { id: 8, name: 'Чайник', category: 'Бытовая техника', price: 3000 },
  { id: 9, name: 'Пылесос', category: 'Бытовая техника', price: 15000 },
  { id: 10, name: 'Рюкзак', category: 'Аксессуары', price: 3500 }
])

// Категории для селекта
const categories = [...new Set(items.value.map(i => i.category))]

</script>


<template>
  <div>
    <h1>Список товаров</h1>

    <!-- Список товаров -->
    <div>
      <div v-for="item in items" :key="item.id">
        <h2>{{ item.name }}</h2>
        <p>Категория: {{ item.category }}</p>
        <p>Цена: {{ item.price }}₽</p>
      </div>
      <p v-if="items.length === 0">Нет товаров по заданным критериям</p>
    </div>
  </div>
</template>

Кратко, что здесь происходит:

  • Массив товаров items состоит из продуктов. Каждый объект продукта имеет название, категорию и цену.

  • С помощью цикла v-for динамически отображаются все продукты на странице.

  • Если элементов для отображения нет, то выводит сообщение об этом.

Теперь добавим шаблон формы и реактивный объект, к которому мы привяжем поля фильтров с помощью v-model:

<script setup>
// ...

const filters = reactive({
  name: '',
  category: '',
  maxPrice: null,
});
</script>

<template>
  <div>
    <input v-model="filters.name" type="text" placeholder="Поиск по названию" />
    <select v-model="filters.category">
      <option value="">Все категории</option>
      <option v-for="cat in categories" :key="cat" :value="cat">{{ cat }}</option>
    </select>
    <input v-model.number="filters.maxPrice" type="number" placeholder="Макс. цена" />
  </div>

  // ...
</template>

Также заметьте, что в привязке цены был использован модификатор .number, который преобразовывает значение в число (по умолчанию в значение попадает строка, несмотря на тип поля number).

Теперь давайте создадим новое реактивное значение, которое будет хранить отфильтрованные товары и с помощью WatchEffect будем отслеживать, что если изменился любой фильтр, то будем проходить по исходным задачам и выбирать те из них, что соответствуют критериям:

const filteredItems = ref([])

watchEffect(() => {
  filteredItems.value = items.value.filter(item => {
    const matchName = item.name.toLowerCase().includes(filters.name.toLowerCase())
    const matchCategory = filters.category ? item.category === filters.category : true
    const matchPrice = filters.maxPrice ? item.price <= filters.maxPrice : true
    return matchName && matchCategory && matchPrice
  })
})

В коде выше мы явно читаем поля реактивного объекта filters, отчего watchEffect срабатывает каждый раз, при изменении любого фильтра.

Осталось лишь подменить items на filteredItems в шаблоне:

<div>
  <div v-for="item in filteredItems" :key="item.id">
    <h2>{{ item.name }}</h2>
    <p>Категория: {{ item.category }}</p>
    <p>Цена: {{ item.price }}₽</p>
  </div>
  <p v-if="filteredItems.length === 0">Нет товаров по заданным критериям</p>
</div>

Теперь фильтрация полностью готова. Но давайте посмотрим на то, как мы можем улучшить код.

Сейчас мы внутри WatchEffect меняем значение одной реактивной переменной – filteredItems. Vue поддерживает встроенный метод computed, напоминающий watchEffect, с тем отличием, что computed обязательно должен возвращать какое-либо значение и записывать его в переменную. Получится следующее:

// Было
const filteredItems = ref([])

watchEffect(() => {
  filteredItems.value = items.value.filter(item => {
    ...
  })
})

// Cтало
const filteredItems = computed(() => items.value.filter(item => {
  ...
}))

Таким образом сделаем вывод:

Если watchEffect своей логикой изменяет значение одной реактивной переменной, то его можно заменить с помощью computed, сразу вернув значение в нужную переменную, которая при этом останется реактивной.

Теперь вы знаете

  1. В каких случаях применять watch и watchEffect.

  2. Способы реализации компонентов поиска, валидации форм и фильтрации товаров по множественным фильтрам.

  3. Несколько полезных библиотек, облегчающих процесс разработки.

К комментариях смело указывайте, как бы Вы улучшили представленные в статье компоненты.

Если статья была Вам полезна, можете также посетить мой Телеграм канал, в котором найдете другую полезную для себя информацию – https://t.me/sanwed_it

Комментарии (15)


  1. Notactic
    07.06.2025 11:05

    Всё конечно хорошо, только в реальной жизни, без везкой причины, от watch и watchEffect, лучше отказатся.

    Ваши примеры тому подтверждение, вы сделали их через слежение, просто потому, что можете. Всё это делается через событие элементов формы, что в дальнейшем проще дебажить.


    1. VyacheslavGajsin
      07.06.2025 11:05

      В реальной жизни, к счастью, никто не следит на событиями элементов формы в vue, особенно в рамках валидации полей

      Обычно есть computed, который сам вычисляет актуальный стейт валидации


      1. ArutaGerman
        07.06.2025 11:05

        Согласен с предыдущим оратором, т.к. не валидацией единой живем. Тот же запрос при вводе в поле поиска удобнее сделать через событие элемента, а не через вотчеры. И computed тут вообще никак не поможет.

        p.s. при желании можно и через него сделать, но это извращение


        1. VyacheslavGajsin
          07.06.2025 11:05

          У вас реактивность на сигналах, почему вы ей не пользуетесь?
          Единственная причина, которую я могу понять - сложность в ее понимании

          Вы буквально привязываете реактивный примитив к элементу формы, и тут computed напрашивается по всем законам логики

          Когда мы говорим про асинхронную валидацию, то тут уже стоит подумать, что удобнее

          К чему тут речь про поиск - я не очень понял, контекст все же про валидацию


          1. ArutaGerman
            07.06.2025 11:05

            Не было контекста про валидацию, равно как и был пример в статье про поиск. Это уже ваше сообщение про валидацию и, если говорить только о валидации или чем-то другом - уход от общего к частному, что вы и делаете. Речь же о более широком использовании.

            И да, я не против computed для обработки валидации, но опять же это не всегда уместно - зависит от требований: вероятно валидировать нужно всего 1 раз при нажатии на сабмит и тогда предпочтительней будет метод, а не вычисляемое свойство, которое запускается на каждое изменение, ведь computed не бесплатен.

            "Не валидацией единой живем"


            1. VyacheslavGajsin
              07.06.2025 11:05

              Прочтите, пожалуйста, первый комментарий в треде:

              "Всё это делается через событие элементов формы, что в дальнейшем проще дебажить."

              Вы же, напротив, зачем-то уходите к общему случаю, когда тут очевидно речь о валидации формы

              Вы написали:
              "вероятно валидировать нужно всего 1 раз при нажатии на сабмит и тогда предпочтительней будет метод, а не вычисляемое свойство, которое запускается на каждое изменение, ведь computed не бесплатен."

              Вероятнее валидировать придется на событии change, либо в режиме realtime, раз уж на то пошло. Некоторые еще любят блокировать кнопку submit, когда валидация не пройдена, что, конечно отвратительно с точки зрения UX. Тем не менее все эти кейсы замечательно работают с computed.

              "ведь computed не бесплатен." - очевидно у вас возникнут проблемы с перфом из-за валидации формы =D.


              1. ArutaGerman
                07.06.2025 11:05

                Вы же, напротив, зачем-то уходите к общему случаю, когда тут очевидно речь о валидации формы .

                Если вам это очевидно - ну ок :) Из-за таких "это же очевидно" потом и возникают "а я думал вы поняли / имели это в виду". Нет, не очевидно, если об этом не сказано прямо. Да и косвенных тому намёков нет. Если вы угадали, что это про валидацию, могу только порадоваться вашей догадливости.

                Вероятнее валидировать придется на событии change, либо в режиме realtime, раз уж на то пошло.

                Я четко написал задание: валидировать форму при сабмите, а это значит, что требование, чтобы пользователь видел ошибки только после клика на сабмит..
                Но возникает вопрос: зачем валидировать через computed, если это можно сделать 1 раз при клике на сабмит, если пользователь узнает о состоянии валидации только при клике на сабмит? Вы предлагаете computed и я не говорю, что это решение плохое - вполне нормальное, но я лишь говорю что дешевле сделать то же самое методом.

                очевидно у вас возникнут проблемы с перфом из-за валидации формы

                всё зависит от конкретной ситуации, ведь это может быть как форма на 2 инпута, так и форма, состоящая из десятков, а то и сотен полей (привет no-code / low-code конструкторы) и вот получили тормозного монстра, потому что computed обсчитывает валидацию на каждый чих в форме, поэтому каждой задаче своё решение и постулировать "В реальной жизни, к счастью, никто не следит на событиями элементов формы в vue, особенно в рамках валидации полей " не стоит. Как уже видно из примеров выше, следим когда это требуется.


                1. VyacheslavGajsin
                  07.06.2025 11:05

                  В целом, теперь мне стало яснее ваше мнение, и я с ним почти полностью согласен

                  Я постулировал, что в огромнейшем большинстве случаев формы не мунструозные, и смысла от погони за мнимым перфом нет.

                  Автор комментария вообще написал "Всё конечно хорошо, только в реальной жизни, без везкой причины, от watch и watchEffect, лучше отказатся."

                  И это уже выдумки автора комментария, и описал он контекст своей фразы во втором абзаце, когда сказал про элементы формы.



            1. VyacheslavGajsin
              07.06.2025 11:05

              "Согласен с предыдущим оратором, т.к. не валидацией единой живем. Тот же запрос при вводе в поле поиска удобнее сделать через событие элемента, а не через вотчеры. И computed тут вообще никак не поможет.

              p.s. при желании можно и через него сделать, но это извращение"

              Можно, пожалуйста, хотя-бы один аргумент про удобство и почему это - извращение?

              Есть масса способов сделать это нормально без подписки на событие инпута, и пример в статье абсолютно нормальный с поиском.


              1. VyacheslavGajsin
                07.06.2025 11:05

                Вопрос снимается, тут я уже вас неправильно понял =D


              1. ArutaGerman
                07.06.2025 11:05

                Можно, пожалуйста, хотя-бы один аргумент про удобство и почему это - извращение?

                Можно:)

                Задача:
                - Сделать строку поиска
                - Введенные значения отправляются на сервер и результаты отдаются на фронт

                РЕШЕНИЕ 1. Делать через computed:
                - реагировать на изменение значения в поиске
                - отправлять запрос на сервер
                ТАК НЕЛЬЗЯ делать и это и назвал извращением, потому что computed не для того создан.

                РЕШЕНИЕ 2. Делать через computed, но при этом computed - лишь часть логики, которая будет выглядеть как:
                - ввели значение в строке поиска: сработала v-model
                - среагировали вотчером на изменение значения переменной
                - записали полученные с сервера данные в новую переменную
                - обработали результаты поиска в computed

                Как-то много телодвижений...

                РЕШЕНИЕ 3. Метод на событие input элемента / компонента строки поиска:
                - ввели значение в строке поиска
                - среагировали на событие элемента / компонента - отправили запрос (1 функция)
                - обработали данные в методе и записали в переменную результата (2-ая функция внутри первой функции)

                Расходов на понимание со стороны разработчика и обслуживание кода со стороны движка получается много меньше и для разработчика много понятнее "что откуда и куда" берётся без распутывания цепочки с вотчерами и компьютедами, т.к. всё стало более явно, а явное всегда лучше.


                1. VyacheslavGajsin
                  07.06.2025 11:05

                  Я понял, я про другое подумал
                  Согласен, этот кейс реально будет извращением


      1. Notactic
        07.06.2025 11:05

        Касаемо конкретно валидации, чаще всего у вас в проекте есть готовое решение для валидации полей, либо своё кастомное, либо какой нибудь open-source(например vuelidate).

        А даже если его нет, использовать события формы - более гибкое решение.


        1. VyacheslavGajsin
          07.06.2025 11:05

          "Мнимая гибкость" - единственный аргумент, который здесь может быть для вас

          Причем она вам понадобится буквально в 5% случаев, и будет всегда являться корнер-кейсом, когда вы откажитесь от реактивности в пользу линейной логики


  1. 4youbluberry
    07.06.2025 11:05

    Хорошая статья для не погруженных, спасибо


  1. VyacheslavGajsin
    07.06.2025 11:05

    у Watch есть опция flush, которая очень важна для понимания, но в статье не упомянута, как и watchPostEffect, watchSyncEffect

    также, есть 3й аргумент эффекта watch onCleanup (у watchEffect он первый), который выполняется когда эффект хочет отработать еще раз, и очень полезен в случаях, когда нам может понадобиться заново запросить какие-то данные, а старый запрос отменить с AbortController

    Также, имхо, стоит добавить про то, как watch и watchEffect собирают реактивные зависимости, для выполнения эффекта, ибо это дает чуть больше понимания инструмента, а также может объяснить новичкам, почему этот код работает так, как он работает

    Еще можно добавить про то, почему watch не уничтожаются, когда мы юзаем композабл не внутри setup, а внутри какого-нибудь метода (effect scope)