Vue events Печать
Добавил(а) microsin   

Здесь показаны несколько практических способов обработки событий проекта Vue.

[Обработка клика на кнопке]

Обработка события onclick вызовом встроенной функции в компоненте MyComponent.vue:

// MyComponent.vue
< script setup>
import MyButton from './Button.vue'; < /script>
< template> < MyButton onclick="console.log('Тест');"> Моя кнопка < /MyButton> < /template>

Обработка события click вызовом пользовательской JavaScript функции компонента:

// MyComponent.vue
< script setup>
import MyButton from './Button.vue';

function myfunc() {
console.log("Вывод в лог из функции компонента"); } < /script>
< template> < MyButton @click="myfunc()"> Моя кнопка < /MyButton> < /template>

Вызов стрелочной функции:

< script setup>
import MyButton from './Button.vue';

const myFuncArrow = () => {
console.log("Вызов стрелочной функции компонента"); } < /script>
< template> < MyButton @click="myFuncArrow()"> Моя кнопка < /MyButton> < /template>

Если параметров у функции нет, то её в @click можно указывать и без скобок:

    < MyButton @click="myfunc">
    < MyButton @click="myFuncArrow">

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

Кроме `onclick`, существует множество других событий для кнопок (и элементов в целом), которые можно разделить на несколько категорий.

1. Основные события мыши. Эти события срабатывают при взаимодействии с помощью мыши или подобного устройства (например, тачпада).

onmousedown — срабатывает в момент нажатия кнопки мыши на элементе (палец прикоснулся к экрану). Это происходит до onclick.

onmouseup — срабатывает в момент отпускания кнопки мыши на элементе (палец оторвался от экрана). onclick = onmousedown + onmouseup в пределах одного элемента.

onmouseenter / onmouseover — когда указатель мыши заходит на область элемента. onmouseenter не всплывает(1).

onmouseleave / onmouseout — когда указатель мыши покидает область элемента. onmouseleave не всплывает(1).

onmousemove — срабатывает при каждом движении мыши над элементом.

oncontextmenu — срабатывает при попытке открытия контекстного меню (обычно клик правой кнопкой мыши). Полезно для отмены стандартного меню.

ondblclick — двойной клик.

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

2. События клавиатуры (актуальны, когда кнопка в фокусе). Часто используются для активации кнопки с клавиатуры (Enter, Space).

onkeydown — клавиша нажата (срабатывает первой, при удержании — повторно).

onkeyup — клавиша отпущена.

onkeypress (устаревшее) — срабатывает при нажатии клавиши, которая генерирует символ (например, буква, цифра). Не рекомендуется к использованию, используйте onkeydown/onkeyup.

3. События фокуса. Кнопка может получать фокус с помощью Tab или клика.

onfocus — элемент получил фокус (например, на него перешли с помощью Tab).

onblur — элемент потерял фокус.

onfocusin / onfocusout — аналоги onfocus/onblur, но события всплывают.

4. События форм (если кнопка внутри < form>).

onsubmit — событие формы, а не кнопки, но срабатывает при отправке формы, которую можно инициировать кнопкой с `type="submit"`.

onreset — аналогично для кнопки сброса формы (`type="reset"`).

5. Универсальные и Touch-события.

onpointerdown, onpointerup, onpointermove и др. — современные универсальные события, которые работают для мыши, пера, тач-интерфейсов.

ontouchstart, ontouchend, ontouchmove — специфичные для сенсорных экранов события.

[Практический пример использования нескольких событий для кнопки]

< button id="myBtn"
        onmousedown="console.log('Кнопку нажали')"
        onmouseup="console.log('Кнопку отпустили')"
        onmouseenter="console.log('Мышь на кнопке')"
        onmouseleave="console.log('Мышь ушла')"
        onfocus="console.log('Кнопка в фокусе')"
        onblur="console.log('Кнопка не в фокусе')"
        onkeydown="(event) => { if(event.code === 'Space') console.log('Нажат пробел на кнопке'); }">
  Нажми меня
< /button>
< script>
// Более современный и рекомендуемый способ через addEventListener
document.getElementById('myBtn').addEventListener('click', function(event) {
console.log('Клик обработан через addEventListener!'); }); < /script>

Важное замечание по использованию:

Сейчас рекомендуется использовать не атрибуты HTML (onclick="..."), а метод addEventListener. Это позволяет:

- Добавлять несколько обработчиков на одно событие.
- Легче управлять кодом.
- Использовать возможности фазинга и всплытия событий.
- Разделять структуру (HTML) и логику (JavaScript).

const button = document.querySelector('#myBtn');
button.addEventListener('click', handler1); button.addEventListener('click', handler2); button.addEventListener('mouseenter', () => console.log('Мышь над кнопкой'));

function handler1() { /* ... */ }
function handler2() { /* ... */ }

Итог: самые часто используемые для кнопок события, помимо onclick — это onmousedown/onmouseup (для тонкого контроля), `onmouseenter`/onmouseleave (для визуальных эффектов) и события клавиатуры (onkeydown), чтобы сделать кнопку доступной.

Это важная деталь в механике событий JavaScript.

[Что такое "всплытие событий" (event bubbling)]

Всплытие — это процесс, при котором событие, произошедшее на вложенном элементе, поднимается (всплывает) вверх по иерархии DOM к родительским элементам.

Пример всплытия:

< div id="parent">
  < button id="child">Нажми меня< /button>
< /div>
< script>
document.getElementById('parent').addEventListener('click', () => {
console.log('Клик на родителе (div)');
});

document.getElementById('child').addEventListener('click', () => {
console.log('Клик на кнопке');
}); < /script>

При клике на кнопку в консоли появится:

1. `Клик на кнопке` (сначала сработал обработчик на самом элементе).
2. `Клик на родителе (div)` (затем событие всплыло к родителю).

Событие как бы "пузырьком" поднимается от цели вверх по дереву DOM.

Теперь про onmouseenter и onmouseleave: эти события не всплывают. Событие срабатывает только на том элементе, на который непосредственно навели курсор (или с которого его увели), и не передается родителям.

Пример с mouseenter/mouseleave (не всплывают):

< div id="parent" style="padding: 20px; background: lightblue;">
  Родитель
  < button id="child" style="margin: 10px;">Кнопка< /button>
< /div>
< script>
const parent = document.getElementById('parent');
const child = document.getElementById('child');

parent.addEventListener('mouseenter', () => console.log('mouseenter: родитель'));
parent.addEventListener('mouseleave', () => console.log('mouseleave: родитель'));

child.addEventListener('mouseenter', () => console.log('mouseenter: кнопка'));
child.addEventListener('mouseleave', () => console.log('mouseleave: кнопка')); < /script>

Что произойдет:

1. При наведении курсора снаружи прямо на кнопку:

   → `mouseenter: кнопка`

   Событие НЕ всплыло к родителю, родитель не получил mouseenter.

2. При уводе курсора с кнопки прямо наружу:

   → `mouseleave: кнопка`

   Событие НЕ всплыло к родителю.

3. При наведении курсора снаружи на родительский div (но не на кнопку):

   → `mouseenter: родитель`

А что с onmouseover и onmouseout? Это их аналоги, которые всплывают.

Пример с mouseover/mouseout (всплывают):

parent.addEventListener('mouseover', () => console.log('mouseover: родитель'));
parent.addEventListener('mouseout', () => console.log('mouseout: родитель'));
child.addEventListener('mouseover', () => console.log('mouseover: кнопка'));
child.addEventListener('mouseout', () => console.log('mouseout: кнопка'));

Что произойдет:

1. При наведении курсора снаружи прямо на кнопку:

   → `mouseover: кнопка`
   → `mouseover: родитель` ← Событие всплыло!

2. При уводе курсора с кнопки прямо наружу:

   → `mouseout: кнопка`
   → `mouseout: родитель` ← Событие всплыло!

Почему это важно? Различия на практике следующие. Проблема с mouseover/mouseout:

Из-за всплытия могут возникать "ложные срабатывания". Рассмотрим пример:

< div id="menu">
  < ul>
    < li>Пункт 1< /li>
    < li>Пункт 2< /li>
  < /ul>
< /div>
< script>
const menu = document.getElementById('menu');
let isInsideMenu = false;

menu.addEventListener('mouseover', () => {
if (!isInsideMenu) {
console.log('Вошел в меню');
isInsideMenu = true;
}
});

menu.addEventListener('mouseout', () => {
console.log('Вышел из меню');
isInsideMenu = false;
}); < /script>

Если мышь переходит с menu на li, произойдет:

1. mouseout на menu (ложное срабатывание!).
2. mouseover на li → всплывает как mouseover на menu снова.

Получается хаотичное переключение состояния.

Решение с mouseenter/mouseleave: эти события срабатывают только при фактическом входе/выходе из всего элемента с потомками, игнорируя переходы между родителем и ребенком.

menu.addEventListener('mouseenter', () => {
  console.log('Вошел в меню (точно!)');
});
menu.addEventListener('mouseleave', () => {
console.log('Вышел из меню (точно!)'); });

Когда что использовать?

1. Используйте mouseenter/mouseleave, когда нужно отследить нахождение курсора внутри всей области элемента (например, для выпадающих меню, тултипов, подсветки карточек).

2. Используйте mouseover/mouseout, когда нужно отследить переходы между конкретными вложенными элементами (например, для сложных интерактивных диаграмм или игровых полей).

Современный подход: в большинстве случаев mouseenter/mouseleave ведут себя более предсказуемо и являются предпочтительным выбором для обработки "входа/выхода" курсора.

[defineEmits: передача событий между компонентами]

Наряду с передачей данных компоненту с помощью defineProps [1] есть также возможность передавать события от одного компонента к другому, опционально с какими-то данными. Это делается с помощью defineEmits.

На входе defineEmits принимает в качестве параметров массив событий: как стандартных наподобие click, так и пользовательских, например myEvent.

Пример использования defineEmits:

// MyComponent.vue
< script setup>
import MyButton from './Button.vue';

const myemit = defineEmits(['myEvent'])

function myfunc() {
myemit('myEvent', "Сгенерировано мое событие"); } < /script>
< template> < MyButton @click="myfunc"> Моя кнопка < /MyButton> < /template>

Здесь myemit принимает первым параметром название определенной через defineEmits функции, и может принять после него произвольное количество параметров. В данном примере передается параметр в виде строки "Сгенерировано мое событие".

Это событие можно принять в родительском компоненте следующим образом. Предположим, что родительский компонент это App.vue, а дочерний компонент это MyComponent.vue:

// App.vue
< script setup>
import CitySelect from "./components/MyComponent.vue"

function getEventData (data) {
console.log(data) } < /script>
< template> < MyComponent @my-event="getEventData"/> < /template>

В этом примере указано, что компонент App.vue слушает событие my-event от компонента MyComponent, вызывая при этом пользовательскую функцию getEventData, которая получит параметры, переданные через myemit. Обратите внимание, что имя события myEvent было преобразовано в my-event, так это принято для передачи событий от компонента.

По этой причине лучше делать определения в defineEmits дочернего компонента консистетными с именами события в родительском компоненте:

// MyComponent.vue
< script setup>
import MyButton from './Button.vue';

const myemit = defineEmits(['my-event'])

function myfunc() {
myemit('my-event', "Сгенерировано мое событие"); } < /script>

[Альтернативные записи emits и их валидация]

В defineEmits можно передать не массив событий (через квадратные скобки), а объект (через фигурные скобки). Это дает дополнительные возможности по проверке событий.

// MyComponent.vue
const myemit = defineEmits({
myEvent: null });

function myfunc() {
myemit('myEvent', "Сгенерировано мое событие"); }

В данном примере валидатор не используется (после myEvent: указано null). Родительский компонент App.vue арим этом не поменялся.

Функция валидации. Следующий пример расширяет определение myEvent добавлением функции валидации события.

// MyComponent.vue
const myemit = defineEmits({
myEvent(payload) {
return payload ? true : false;
}, });

function myfunc() {
myemit('myEvent', "Сгенерировано мое событие"); }

В этом примере если переданные данные payload присутствуют, то событие возвратит true.

Переданные данные можно также возвратить и вывести в консоль, например:

// MyComponent.vue
const myemit = defineEmits({
myEvent(payload) {
console.log('Проверка payload: ${payload}')
return payload;
}, });

defineEmits это макрос компилятора Vue 3 Composition API (чаще всего используется в однофайловых компонентах < script setup>), который позволяет компоненту объявлять и генерировать пользовательские события.

В Vue компоненты могут общаться с родительскими компонентами через события. defineEmits — это способ объявить, какие события может генерировать дочерний компонент.

[Синтаксис defineEmits]

1. Runtime объявление (без валидации)

< script setup>
// Объявляем, что компонент может генерировать события 'submit' и 'cancel'
const emit = defineEmits(['submit', 'cancel']);

function handleSubmit() {
// Генерируем событие 'submit' с данными
emit('submit', { id: 1, data: 'test' }); }

function handleCancel() {
// Генерируем событие 'cancel' без данных
emit('cancel'); } < /script>

2. Type-based объявление (с TypeScript)

< script setup lang="ts">
// Более строгое объявление с типами
const emit = defineEmits< {
// Событие 'submit' с обязательным объектом данных
(e: 'submit', data: { id: number; name: string }): void;

// Событие 'cancel' без данных
(e: 'cancel'): void;

// Событие 'update' с необязательным параметром
(e: 'update', value?: string): void; }>();

function sendData() {
emit('submit', { id: 1, name: 'John' }); // OK
// emit('submit', { id: 1 }); // Ошибка TypeScript: нет поля name
// emit('submit', 'test'); // Ошибка TypeScript: неверный тип } < /script>

3. Object синтаксис (с валидацией)

< script setup>
const emit = defineEmits({
// Валидация события 'submit'
submit: ({ email, password }) => {
if (!email || !password) {
console.warn('Отсутствуют email или password');
return false; // Событие не будет сгенерировано
}
return true; // Событие будет сгенерировано },

// Событие без валидации
cancel: null });

function trySubmit() {
const isValid = emit('submit', {
email: 'user@example.com',
password: '123'
});
// isValid будет true или false в зависимости от валидации } < /script>

[Как использовать в родительском компоненте]

< !-- ParentComponent.vue -->
< template>
  < ChildComponent 
    @submit="handleSubmit" 
    @cancel="handleCancel"
    @update="handleUpdate"
  />
< /template>
< script setup>
import ChildComponent from './ChildComponent.vue';

function handleSubmit(data) {
console.log('Получены данные:', data); // { id: 1, name: 'John' } }

function handleCancel() {
console.log('Операция отменена'); }

function handleUpdate(value) {
console.log('Обновлено:', value); } < /script>

[Отличия от Vue 2 Options API]

Vue 2 (Options API):

< script>
export default {
emits: ['submit', 'cancel'], // Объявление в опции emits
methods: {
sendData() {
this.$emit('submit', data); // Используем this.$emit
}
} } < /script>

Vue 3 (Composition API):

< script setup>
const emit = defineEmits(['submit', 'cancel']); // defineEmits в < script setup>

function sendData() {
emit('submit', data); // Используем emit напрямую } < /script>

Ключевые особенности:

1. Только в < script setup>: defineEmits работает только внутри < script setup> и не требует импорта.

2. Компиляционный макрос: код компилируется во время сборки и не выполняется в runtime.

3. TypeScript поддержка: позволяет создавать строго типизированные события.

4. Валидация: можно добавлять валидацию для событий.

5. Возвращаемое значение: emit() возвращает true, если событие прошло валидацию, или false, если нет.

[Пример реального использования]

< !-- ModalDialog.vue -->
< template>
  < div class="modal" v-if="isVisible">
    < div class="modal-content">
      < slot>< /slot>
      < button @click="confirm">OK< /button>
      < button @click="close">Cancel< /button>
    < /div>
  < /div>
< /template>
< script setup>
import { ref } from 'vue';

const props = defineProps({
title: String });

// Объявляем события, которые может генерировать модальное окно
const emit = defineEmits(['confirm', 'cancel', 'close']);
const isVisible = ref(true);
function confirm() {
// Генерируем событие с данными
emit('confirm', {
timestamp: new Date(),
message: 'Пользователь подтвердил действие'
});
close(); }

function close() {
emit('close'); isVisible.value = false; }

// Если пользователь нажал на фон
function onBackgroundClick() {
emit('cancel', 'Отменено по клику вне модального окна');
close(); } < /script>

< !-- ParentComponent.vue --> < template> < button @click="showModal = true">Открыть модалку< /button> < ModalDialog v-if="showModal" @confirm="handleConfirm" @cancel="handleCancel" @close="showModal = false" > Вы уверены, что хотите удалить запись? < /ModalDialog> < /template>
< script setup>
import { ref } from 'vue';
import ModalDialog from './ModalDialog.vue';

const showModal = ref(false);

function handleConfirm(data) {
console.log('Подтверждено:', data);
// Удаляем запись... }

function handleCancel(reason) {
console.log('Отменено:', reason); } < /script>

[Важные моменты]

1. Именование событий: рекомендуется использовать kebab-case для имен событий в шаблонах (@my-event), но в defineEmits можно использовать camelCase.

2. Не используйте с обычным < script>: 

< script>
// Это НЕ сработает!
const emit = defineEmits(['submit']); // ❌ Ошибка < /script>

3. Глобальная доступность: в < script setup> defineEmits, defineProps, defineExpose доступны глобально без импорта.

[Ссылки]

1. Vue defineProps.