События компонентов
Подразумевается, что вы уже изучили и разобрались с разделом Основы компонентов. Если нет — прочитайте его сначала.
Отправка и прослушивание событий
Компонент может генерировать пользовательские события непосредственно в выражениях шаблона (например, в обработчике v-on
), используя встроенный метод $emit
:
template
<!-- MyComponent -->
<button @click="$emit('someEvent')">нажми на меня</button>
Родительский компонент может прослушать его, используя v-on
:
template
<MyComponent @some-event="callback" />
Модификатор .once
также поддерживается для слушателей событий компонента:
template
<MyComponent @some-event.once="callback" />
Как компоненты и входные параметры, имена событий обеспечивают автоматическое преобразование регистра. Обратите внимание, что мы испустили событие в регистре camelCase, но можем прослушать его с помощью слушателя в регистре kebab-case в родительском компоненте. Как и в случае с регистрацией входных параметров, мы рекомендуем использовать kebab-case регистр для прослушивания событий в шаблонах.
Совет
В отличие от собственных событий DOM, события, испускаемые компонентами, не всплывают. Вы можете слушать только события, испускаемые непосредственно дочерним компонентом. Если необходимо взаимодействие между родственными или глубоко вложенными компонентами, используйте внешнюю шину событий или глобальное управление состоянием.
Аргументы события
Иногда бывает полезно передать определенное значение вместе с событием. Например, мы можем захотеть, чтобы компонент <BlogPost>
отвечал за то, насколько увеличить текст. В таких случаях мы можем передать $emit
дополнительные аргументы, чтобы предоставить это значение:
template
<button @click="$emit('increaseBy', 1)">
Увеличить на 1
</button>
Затем, когда мы прослушиваем событие в родителе, мы можем использовать встроенную стрелочную функцию в качестве слушателя, что позволит нам получить доступ к аргументу события:
template
<MyButton @increase-by="(n) => count += n" />
Или, если обработчик события является методом:
template
<MyButton @increase-by="increaseCount" />
Затем значение будет передано в качестве первого параметра этого метода:
js
function increaseCount(n) {
count.value += n
}
Совет
Все дополнительные аргументы, переданные в $emit()
после имени события, будут переданы слушателю. Например, при $emit('foo', 1, 2, 3)
функция обработчика события получит три аргумента.
Определение пользовательских событий
События генерируемые компонентом можно объявить с помощью defineEmits()
:
vue
<script setup>
defineEmits(['inFocus', 'submit'])
</script>
Метод $emit
, который мы использовали в <template>
недоступен в разделе <script setup>
компонента, но defineEmits()
возвращает эквивалентную функцию, которую мы можем использовать вместо него:
vue
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() {
emit('submit')
}
</script>
Макрос defineEmits()
нельзя использовать внутри функции, он должен быть помещен непосредственно в <script setup>
, как в примере выше.
Если вы используете явную функцию setup
вместо <script setup>
события должны быть объявлены с помощью опции emits
, а функция emit
доступна в контексте setup()
:
js
export default {
emits: ['inFocus', 'submit'],
setup(props, ctx) {
ctx.emit('submit')
}
}
Как и другие свойства контекста setup()
, emit
можно безопасно деструктурировать:
js
export default {
emits: ['inFocus', 'submit'],
setup(props, { emit }) {
emit('submit')
}
}
Опция emits
также поддерживает синтаксис объекта, что позволяет нам выполнять проверку полезной нагрузки пользовательских событий во время выполнения:
vue
<script setup>
const emit = defineEmits({
submit(payload) {
// возвращает `true` или `false`, чтобы показать
// что проверка пройдена / не пройдена
}
})
</script>
Если вы используете TypeScript с <script setup>
, можно также объявить генерируемые события с помощью чистых аннотаций типов:
vue
<script setup lang="ts">
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
Подробнее: Типизирование пользовательских событий компонента
Хотя это и необязательно, рекомендуется определить все генерируемые события, чтобы лучше документировать, как должен работать компонент. Это также позволяет Vue исключать известных слушателей из проваливающихся атрибутов, избегая пограничных случаев, вызванных событиями DOM, которые вручную отправляются сторонним кодом.
Совет
Если в опции emits
определено собственное событие (например, click
), слушатель теперь будет прослушивать только события click
, генерируемые компонентом, и больше не будет реагировать на собственные события click
.
Валидация сгенерированных событий
Аналогично валидации входных параметров, генерируемые события также могут быть валидированы, если это определено с помощью объектного синтаксиса или синтаксиса массива.
Для добавления валидации событию необходимо указать функцию, которая получает аргументы, с которыми вызывался emit
и возвращает булево, определяющее является ли событие корректным или нет.
vue
<script setup>
const emit = defineEmits({
// Без валидации
click: null,
// Валидация события submit
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Некорректные данные для события submit!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', { email, password })
}
</script>
Использование вместе с v-model
Пользовательские события также можно использовать для создания пользовательских входных данных, которые работают с v-model
. Давайте рассмотрим, как v-model
используется на нативном элементе:
template
<input v-model="searchText" />
Под капотом компилятор шаблонов расширяет v-model
до более точного эквивалента для нас. Таким образом, приведенный выше код делает то же самое, что и следующий:
template
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
Вместо этого при использовании на компоненте, v-model
расширяется до этого:
template
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
Для того чтобы это действительно работало, компонент <CustomInput>
должен выполнять две вещи:
- Привязать атрибут
value
нативного элемента<input>
к входному параметруmodelValue
- При срабатывании нативного события
input
, сгенерировать пользовательское событиеupdate:modelValue
с новым значением
Вот как это выглядит:
vue
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
Теперь v-model
должна отлично работать с этим компонентом:
template
<CustomInput v-model="searchText" />
Другой способ реализации v-model
в этом компоненте - использовать computed
свойство с геттером и сеттером. Метод get
должен возвращать свойство modelValue
, а метод set
генерировать соответствующее событие:
vue
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
<template>
<input v-model="value" />
</template>
Аргументы v-model
При использовании v-model
на компоненте по умолчанию используется входной параметр modelValue
и событие update:modelValue
. Их можно изменить с помощью аргумента v-model
:
template
<MyComponent v-model:title="bookTitle" />
В таком случае, ожидается что дочерний компонент будет использовать входной параметр title
и генерировать событие update:title
для синхронизации значения:
vue
<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
Multiple v-model
bindings
Развивая потенциал возможности определять конкретный входной параметр и событие, как мы изучили ранее с помощью аргумента v-model
, теперь стало возможным создавать несколько привязок v-model на одном экземпляре компонента.
Каждый v-model синхронизирует свой входной параметр, без необходимости дополнительных опций в компоненте:
template
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
vue
<script setup>
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
Обработка модификаторов v-model
Изучая работу с формами, можно заметить, что у v-model
есть встроенные модификаторы - .trim
, .number
и .lazy
. Но в некоторых случаях может потребоваться добавить пользовательские модификаторы.
Создадим для примера пользовательский модификатор capitalize
, который станет делать заглавной первую букву строки, привязанной с помощью v-model
:
template
<MyComponent v-model.capitalize="myText" />
Модификаторы, которые будут использоваться в v-model
компонента, нужно указывать через входной параметр modelModifiers
. В примере ниже, у компонента есть входной параметр modelModifiers
, который по умолчанию будет пустым объектом.
vue
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
defineEmits(['update:modelValue'])
console.log(props.modelModifiers) // { capitalize: true }
</script>
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
Обратите внимание, в хуке жизненного цикла created
входной параметр modelModifiers
будет содержать capitalize
со значением true
- потому что он указан на привязке v-model
компонента v-model.capitalize="myText"
.
Теперь, после настройки входного параметра, можно проверять ключи modelModifiers
и создать обработчик для изменения значения. Например, будем запускать этот обработчик каждый раз, когда элемент <input />
генерирует событие input
.
vue
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
При использовании привязки v-model
с аргументом, имя входного параметра будет генерироваться как arg + "Modifiers"
:
template
<MyComponent v-model:title.capitalize="myText">
Соответствующие объявления должны быть:
js
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])
console.log(props.titleModifiers) // { capitalize: true }