Как работает v-model и модификаторы событий?
Вопрос
Как устроена директива v-model для двусторонней привязки и что делают модификаторы событий вроде .prevent, .stop, .once?
Короткий ответ
v-model — это синтаксический сахар, который под капотом разворачивается в привязку значения (:value) плюс обработчик события ввода (@input), обновляющий это значение. Модификаторы событий (.prevent, .stop, .once и др.) — это декларативный способ вызвать event.preventDefault(), event.stopPropagation() или ограничить срабатывание обработчика, не прописывая это вручную в теле функции.
Подробный ответ
v-model. Для обычного <input> запись:
<input v-model="text" />
эквивалентна:
<input :value="text" @input="text = $event.target.value" />
То есть v-model объединяет вывод значения в атрибут и обновление переменной при событии ввода в одну директиву. Для разных элементов Vue использует разные пары событие/атрибут: <input type="checkbox"> использует checked/change, <select> — value/change и т.д. — директива сама выбирает подходящую связку.
На пользовательских компонентах v-model разворачивается в проп modelValue + событие update:modelValue (см. вопрос про props/emits) — компонент должен объявить defineModel() (или вручную defineProps(['modelValue']) + defineEmits(['update:modelValue'])), чтобы поддержать эту привязку.
Модификаторы событий. Пишутся через точку после имени события в v-on:
.prevent— вызываетevent.preventDefault()(например, чтобы форма не перезагружала страницу приsubmit)..stop— вызываетevent.stopPropagation(), останавливая всплытие события к родителям..once— обработчик сработает только один раз, дальше снимается автоматически..self— обработчик сработает, только если событие произошло на самом элементе, а не всплыло от дочернего..capture— навешивает обработчик в фазе перехвата (capture), а не всплытия.- Модификаторы клавиш (
@keyup.enter,@keyup.esc) — фильтруют обработчик по конкретной клавише.
Модификаторы можно комбинировать (@click.stop.prevent) и они читаются в порядке написания слева направо, что важно, например, для .self в сочетании с другими.
Также у v-model есть свои модификаторы: .lazy (обновлять по change, а не по input), .number (приводить к числу), .trim (обрезать пробелы).
Пример
<script setup>
import { ref } from 'vue'
const email = ref('')
const submitted = ref(false)
function handleSubmit() {
submitted.value = true
console.log('Отправлено:', email.value)
}
</script>
<template>
<!-- .prevent — не даём браузеру перезагрузить страницу при submit -->
<form @submit.prevent="handleSubmit">
<!-- v-model.trim — сахар над :value + @input, плюс обрезка пробелов -->
<input v-model.trim="email" type="email" placeholder="Email" />
<button type="submit">Отправить</button>
</form>
<!-- .stop — не даём клику всплыть к обёртке; .once — сработает один раз -->
<div @click="console.log('клик по контейнеру')">
<button @click.stop.once="() => console.log('клик по кнопке, только один раз')">
Нажми меня
</button>
</div>
<p v-if="submitted">Форма отправлена: {{ email }}</p>
</template>
Дополнительные вопросы
- Как реализовать
v-modelна собственном компоненте с полем ввода? - В чём разница между модификаторами
.stopи.self? - Что делает модификатор
.syncи почему он был заменён наv-model:propNameв Vue 3?