← Курс/Обработка событий в Vue#205 из 257+30 XP

Обработка событий в Vue

v-on и сокращение @

Для обработки DOM-событий в Vue используется директива v-on или её сокращение @:

<template>
  <!-- Полная форма -->
  <button v-on:click="handleClick">Нажми</button>

  <!-- Сокращённая форма (используется чаще) -->
  <button @click="handleClick">Нажми</button>

  <!-- Передача аргументов -->
  <button @click="greet('Алексей')">Поздороваться</button>

  <!-- Доступ к объекту события -->
  <button @click="handleClick($event)">С событием</button>
</template>

<script setup>
function handleClick(event) {
  console.log('Клик!', event.target)
}

function greet(name) {
  console.log(`Привет, ${name}!`)
}
</script>

Inline handlers vs Methods

<!-- Inline — для простой логики -->
<button @click="count++">+1</button>
<button @click="items.push(newItem)">Добавить</button>

<!-- Method — для сложной логики -->
<button @click="submitForm">Отправить</button>

Модификаторы событий

Vue предоставляет **модификаторы** — удобные суффиксы для распространённых операций:

<!-- .prevent — вызывает event.preventDefault() -->
<form @submit.prevent="handleSubmit">...</form>
<a @click.prevent="navigate" href="/page">Ссылка</a>

<!-- .stop — вызывает event.stopPropagation() -->
<div @click="outerClick">
  <button @click.stop="innerClick">Клик не всплывает</button>
</div>

<!-- .once — обработчик сработает только один раз -->
<button @click.once="sendAnalytics">Первый клик</button>

<!-- .self — только если клик именно на элемент (не на дочерний) -->
<div @click.self="handleSelfClick">
  <span>Клик на span не вызовет handleSelfClick</span>
</div>

<!-- Можно цеплять несколько модификаторов -->
<form @submit.prevent.stop="handleSubmit">...</form>

Модификаторы клавиш

<!-- Только при нажатии Enter -->
<input @keyup.enter="submit">

<!-- Только при нажатии Escape -->
<input @keyup.esc="cancel">

<!-- Ctrl + S -->
<input @keyup.ctrl.s="save">

<!-- Любая клавиша-стрелка -->
<div @keydown.arrow-up="moveUp">
<div @keydown.arrow-down="moveDown">

Пользовательские события компонентов

Компонент может генерировать собственные события через emit:

<!-- ChildComponent.vue -->
<template>
  <button @click="handleClick">Отправить данные родителю</button>
</template>

<script setup>
const emit = defineEmits(['data-sent', 'error'])

function handleClick() {
  emit('data-sent', { value: 42, timestamp: Date.now() })
}
</script>
<!-- ParentComponent.vue -->
<template>
  <ChildComponent @data-sent="handleData" @error="handleError" />
</template>

<script setup>
function handleData(payload) {
  console.log('Получено от ребёнка:', payload)
}
</script>

EventEmitter — паттерн в основе событий

Система событий Vue (и Node.js, и браузерного DOM) основана на паттерне **EventEmitter** (или Pub/Sub). Это позволяет компонентам общаться без прямых ссылок друг на друга:

  • **Издатель** (emit) — генерирует события
  • **Подписчик** (on) — слушает события
  • **Отписка** (off) — прекращает слушать
  • **Одноразовый** (once) — слушает ровно один раз
  • Примеры

    EventEmitter — паттерн событий, лежащий в основе системы событий Vue

    // EventEmitter — фундаментальный паттерн для работы с событиями.
    // Vue использует его для компонентных событий, Node.js — для I/O,
    // браузер — для DOM-событий.
    
    class EventEmitter {
      constructor() {
        // Map: имя события -> массив обработчиков
        this._events = new Map()
      }
    
      // Подписаться на событие
      on(event, handler) {
        if (!this._events.has(event)) {
          this._events.set(event, [])
        }
        this._events.get(event).push(handler)
        console.log(`[on] Подписан на "${event}"`)
        return this  // для цепочки вызовов
      }
    
      // Отписаться от события
      off(event, handler) {
        if (!this._events.has(event)) return this
        const handlers = this._events.get(event)
        const index = handlers.indexOf(handler)
        if (index > -1) {
          handlers.splice(index, 1)
          console.log(`[off] Отписан от "${event}"`)
        }
        return this
      }
    
      // Генерировать событие
      emit(event, ...args) {
        if (!this._events.has(event)) return this
        const handlers = this._events.get(event)
        console.log(`[emit] "${event}" -> ${handlers.length} обработчиков`)
        handlers.forEach(handler => handler(...args))
        return this
      }
    
      // Подписаться один раз (автоматическая отписка после первого вызова)
      once(event, handler) {
        const wrapper = (...args) => {
          handler(...args)
          this.off(event, wrapper)  // отписываемся сразу после вызова
        }
        return this.on(event, wrapper)
      }
    }
    
    // --- Демонстрация ---
    
    const emitter = new EventEmitter()
    
    function onData(data) {
      console.log('Получены данные:', data)
    }
    
    function onDataExtra(data) {
      console.log('Дополнительный обработчик:', data)
    }
    
    // Подписываемся
    emitter.on('data', onData)
    emitter.on('data', onDataExtra)
    
    // Одноразовый обработчик
    emitter.once('connect', () => {
      console.log('Подключение установлено (сработает только один раз)')
    })
    
    console.log('\n--- Первый emit ---')
    emitter.emit('data', { value: 42 })
    
    console.log('\n--- connect: первый раз ---')
    emitter.emit('connect')
    
    console.log('\n--- connect: второй раз (once уже отписан) ---')
    emitter.emit('connect')
    
    console.log('\n--- Отписываем onDataExtra ---')
    emitter.off('data', onDataExtra)
    emitter.emit('data', { value: 100 })  // только onData