defineEmits оголошує, які події компонент може випромінювати і з якими аргументами.
Сучасний синтаксис (Vue 3.3+) - іменовані кортежі:
<script setup lang="ts">
const emit = defineEmits<{
change: [id: number]
update: [value: string, source: 'keyboard' | 'paste']
close: []
}>()
emit('change', 42)
emit('update', 'текст', 'paste')
emit('change', 'abc') // помилка TypeScript
emit('unknown') // помилка TypeScript
</script>
Старіший синтаксис - сигнатури викликів, працює так само:
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'close'): void
}>()
Батьківський компонент отримує перевірку типів у шаблоні: @change="(id) => select(id)" - id має тип number, а підписка на неоголошену подію підсвічується редактором (з розширенням Vue - Official і vue-tsc).
Навіщо оголошувати події взагалі:
- документація компонента: з першого рядка видно його «вихідний» інтерфейс;
- атрибути-слухачі не протікають на корінь: оголошена подія не потрапляє у
$attrs. Неоголошена - потрапляє і може навісити нативний обробник на кореневий елемент, через що@clickспрацює двічі; - перевірка в режимі розробки: з runtime-валідацією (об'єктний синтаксис з функціями-валідаторами) Vue попередить про некоректне корисне навантаження.
Типові помилки:
- події в стилі camelCase і kebab-case: у шаблоні батька пишуть
@item-selected, у дочірньомуemit('itemSelected')- Vue їх зіставляє, але в типах і пошуку по коду краще одна форма; - зміна props замість події: дочірній компонент мутує об'єкт із props («він же не примітив, працює») - потік даних стає непрозорим. Зміну даних батька - лише через подію чи
defineModel; - занадто багато подій - знак, що компонент робить забагато або що частину стану варто винести в стор.