Kurs Vue.js · Moduł 7: Props i zdarzenia

Custom Events

4 min czytania
W tej lekcji4

W Stacji Komunikacji sygnał nie idzie tylko w jedną stronę! Gdy Mars odbierze dane, wysyła potwierdzenie z powrotem na Ziemię. W Vue dziecko komunikuje się z rodzicem przez custom events - sygnały emitowane w górę drzewa komponentów.

Props płyną wyłącznie w dół, więc przycisk w module nadawczym nie może sam zmienić stanu centrali. Operator klika "Wyślij" i co dalej? Przycisk nie jest właścicielem dziennika łączności, trzyma go rodzic. Potrzebny jest kanał zwrotny: dziecko ogłasza, że coś się stało, a rodzic decyduje, co z tym zrobić.

defineEmits

W <script setup> deklarujesz emitowane zdarzenia makrem defineEmits. Tak jak defineProps, nie wymaga ono importu. Zwraca funkcję emit: jej pierwszym argumentem jest nazwa zdarzenia, a kolejnymi dane, które chcesz wysłać, czyli payload:

1<!-- SignalButton.vue (dziecko) -->
2<script setup>
3const emit = defineEmits(['signal-sent', 'error'])
4
5const sendSignal = () => {
6  emit('signal-sent', { message: 'OK', time: Date.now() })
7}
8
9const reportError = (code) => {
10  emit('error', code)
11}
12</script>
13
14<template>
15  <button @click="sendSignal">Send</button>
16</template>

Lista w defineEmits działa jak rejestr kanałów modułu: od razu widać, jakie sygnały komponent potrafi nadać. Jeśli wyemitujesz zdarzenie spoza listy, Vue w trybie deweloperskim ostrzeże Cię w konsoli. Samo wywołanie emit niczego nie zmienia w dziecku - tylko nadaje komunikat.

Nasłuchiwanie w rodzicu

Rodzic nasłuchuje zdarzeń przez @event-name lub v-on:event-name, dokładnie tak jak kliknięć w zwykłe elementy HTML:

1<!-- Parent.vue -->
2<template>
3  <SignalButton
4    @signal-sent="handleSignal"
5    @error="handleError"
6  />
7</template>
8
9<script setup>
10const handleSignal = (data) => {
11  console.log('Signal received:', data.message)
12}
13
14const handleError = (code) => {
15  console.error('Error:', code)
16}
17</script>

Handler dostaje payload jako swoje argumenty, dlatego handleSignal może odczytać data.message. Vue dopasowuje przy tym zapis nazw: zdarzenie wyemitowane jako signalSent złapiesz przez @signal-sent. Dokumentacja poleca camelCase w emit i kebab-case w szablonie rodzica, ale kebab-case po obu stronach, jak w tym przykładzie, też działa. Najważniejsza różnica względem zdarzeń DOM: zdarzenia komponentów nie wędrują w górę drzewa. Słyszy je tylko bezpośredni rodzic, a komponent dwa piętra wyżej nie dostanie sygnału, chyba że rodzic przekaże go dalej.

Event Payload

Emit może przekazywać dowolne dane jako payload - pojedynczą wartość, kilka argumentów albo cały obiekt:

1<script setup>
2const emit = defineEmits(['submit'])
3
4const onSubmit = () => {
5  // Jeden argument
6  emit('submit', 'data')
7
8  // Wiele argumentów
9  emit('submit', name, email, role)
10
11  // Obiekt
12  emit('submit', { name, email, role })
13}
14</script>

Wszystkie argumenty po nazwie zdarzenia trafiają do handlera w tej samej kolejności. Polecam trzecią formę, czyli jeden obiekt: łatwiej dopisać do niego nowe pole, a rodzic nie musi pamiętać, który argument był który.

Walidacja Eventów

Obiektowa składnia defineEmits pozwala sprawdzić payload przed wysłaniem. Kluczem jest nazwa zdarzenia, a wartością null (bez walidacji) albo funkcja, która dostaje argumenty emit i zwraca true lub false:

1<script setup>
2const emit = defineEmits({
3  // Bez walidacji
4  click: null,
5
6  // Z walidacją
7  submit: (payload) => {
8    if (!payload.email) return false
9    if (!payload.email.includes('@')) return false
10    return true
11  }
12})
13</script>

Tak jak przy props, nieudana walidacja tylko ostrzega w konsoli w trybie deweloperskim - zdarzenie i tak zostanie wysłane, więc rodzic nadal powinien być gotowy na złe dane. W tym przykładzie kryje się też pułapka: linia click: null deklaruje nazwę zdarzenia natywnego. Od tej chwili nasłuchiwacz @click w rodzicu reaguje wyłącznie na zdarzenia wyemitowane przez komponent, a nie na zwykłe kliknięcia w jego główny element.

Moja rada: deklaruj każde zdarzenie, nawet gdy nie potrzebujesz walidacji. Rejestr kanałów to najkrótsza dokumentacja modułu, a ostrzeżenia o niezadeklarowanych sygnałach wyłapią literówki w nazwach. W edytorze poniżej dwa przyciski nadają ten sam sygnał, a rodzic zbiera je w jednym dzienniku. Props w dół i zdarzenia w górę tworzą razem pełny obieg danych - w następnej lekcji zobaczysz, jak Vue skleja te dwa kanały w jedną dyrektywę v-model.

Zapamiętaj: props to rozkaz z Ziemi, a emit to potwierdzenie z Marsa - dwa kanały, każdy w swoją stronę.

Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3import SignalButton from './SignalButton.vue'
4
5const messages = ref([])
6
7const handleSignal = (data) => {
8  messages.value.push(data)
9}
10</script>
11
12<template>
13  <div class="nova-lab">
14    <h1>Demo: custom events</h1>
15
16    <SignalButton
17      label="Wyślij sygnał Alfa"
18      @signal-sent="handleSignal"
19    />
20    <SignalButton
21      label="Wyślij sygnał Beta"
22      @signal-sent="handleSignal"
23    />
24
25    <div class="log">
26      <h3>Odebrane sygnały:</h3>
27      <p v-for="(msg, i) in messages" :key="i">
28        {{ msg.timestamp }} - {{ msg.label }}: {{ msg.message }}
29      </p>
30      <p v-if="!messages.length" class="empty">Nie odebrano jeszcze żadnego sygnału...</p>
31    </div>
32  </div>
33</template>
34
35<style scoped>
36.nova-lab {
37  background: #0a0e27;
38  color: #00ff88;
39  min-height: 100vh;
40  padding: 20px;
41  font-family: monospace;
42}
43h1 { color: #00ff88; margin-bottom: 20px; }
44.log {
45  margin-top: 20px;
46  padding: 15px;
47  background: rgba(0, 180, 216, 0.1);
48  border: 2px solid #00b4d8;
49}
50.log h3 { color: #00b4d8; margin-bottom: 10px; }
51.empty { color: #666; font-style: italic; }
52</style>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co to są custom events w Vue?

  2. 2. Jak wyemitować event w <script setup>?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Stwórz komponent emitujący event 'signal-received' z payload po kliknięciu przycisku.

  • Układanie w pionie

    Uporządkuj kroki przepływu custom event:

  • Klikanie w kolejności

    Ułóż kod emitowania eventu z danymi:

  • Edytor kodu

    Stwórz komponent z defineEmits z walidacją payloadu (obiektowa składnia).

Przydatne artykuły