Rozdział 9 z 11

Kurs Vue 3: zdarzenia z potomka do rodzica (defineEmits)

zweryfikowano 2 września 2026 · 3 min

Szybka odpowiedź

Komponent potomny sygnalizuje rodzicowi zmianę, emitując zdarzenie: defineEmits je deklaruje, emit('add-to-cart', id) je wysyła, a rodzic nasłuchuje przez @add-to-cart. Propsy schodzą w dół, zdarzenia idą w górę.

Na koniec tego rozdziału, będziesz umieć przekazać informację z komponentu do jego rodzica.

Propsy schodzą od rodzica do potomka. Żeby przekazać informację w górę, komponent emituje zdarzenie, a rodzic go nasłuchuje, dokładnie tak, jak nasłuchiwałby kliknięcia.

Problem

Od podziału z rozdziału 8 koszyk mieszka w App.vue, a przycisk w ProductDisplay.vue. Potomek nie może zmienić cart: nie ma do niego dostępu, a komponent nie powinien pisać po stanie innego komponentu.

Właściwe sformułowanie to nie „potomek dodaje do koszyka”, tylko „potomek ogłasza, że ktoś kliknął, a rodzic decyduje, co z tym zrobić”.

Zadeklarować i wyemitować

W potomku zadeklaruj emitowane zdarzenia, a potem emituj:

src/components/ProductDisplay.vue
<script setup>
const emit = defineEmits(['add-to-cart'])

function addToCart() {
  emit('add-to-cart')
}
</script>

<template>
  <button
    class="button"
    :class="{ disabledButton: !inStock }"
    :disabled="!inStock"
    @click="addToCart"
  >
    Ajouter au panier
  </button>
</template>

defineEmits to makro kompilatora, tak samo jak defineProps: bez importu. Zwraca funkcję, która służy do emitowania.

Deklarowanie zdarzeń nie jest obowiązkowe, ale się przydaje: lista dokumentuje kontrakt komponentu, edytor podpowiada ją automatycznie, a Vue ostrzega, kiedy wyemitujesz niezadeklarowaną nazwę.

Nasłuch po stronie rodzica

Własnego zdarzenia nasłuchuje się tą samą składnią co zdarzenia natywnego:

src/App.vue
<script setup>
import { ref } from 'vue'
import ProductDisplay from './components/ProductDisplay.vue'

const cart = ref(0)

function updateCart() {
  cart.value += 1
}
</script>

<template>
  <div class="cart">Panier ({{ cart }})</div>
  <ProductDisplay :premium="premium" @add-to-cart="updateCart" />
</template>

Kliknij: licznik znów rusza. Przycisk jest w potomku, licznik w rodzicu i żaden z nich nie zna stanu drugiego.

Przekazanie danych

Realistyczny koszyk pamięta, co się do niego wkłada, a nie tylko ile. Przekaż identyfikator wariantu jako drugi argument emit:

src/components/ProductDisplay.vue
<script setup>
function addToCart() {
  emit('add-to-cart', variants.value[selectedVariant.value].id)
}
</script>

Rodzic dostaje go jako parametr swojej funkcji:

src/App.vue
<script setup>
const cart = ref([])

function updateCart(id) {
  cart.value.push(id)
}
</script>

<template>
  <div class="cart">Panier ({{ cart.length }})</div>
  <ProductDisplay :premium="premium" @add-to-cart="updateCart" />
</template>

cart stał się tablicą, więc wyświetlanie korzysta z cart.length. Dodaj kilka razy niebieski T-shirt, a potem sprawdź w narzędziach deweloperskich Vue, że tablica faktycznie zapełnia się wartościami 2234.

Przy kilku informacjach emituj obiekt zamiast długiej listy argumentów:

javascript
emit('add-to-cart', { id: variant.id, taille: 'M', quantite: 1 })

Nazwy zdarzeń

Nazwę pisz w kebab-case: add-to-cart, nie addToCart. Atrybuty HTML nie rozróżniają wielkości liter, szablon napisany wprost w stronie dostałby @addtocart i nasłuch nigdy by się nie odpalił. W pliku .vue Vue nadrabia wielkość liter, ale lepiej od razu wyrobić sobie dobry nawyk.

Zdarzenia nie idą w górę same z siebie

Własne zdarzenie propaguje się tylko do bezpośredniego rodzica. Jeśli nadawcę od odbiorcy dzielą trzy poziomy komponentów, trzeba je reemitować na każdym piętrze. Kiedy staje się to męczące, to sygnał, że potrzebujesz współdzielonego stanu: dokładnie do tego służy Pinia.

Kierunek obiegu

Opinię, która przychodzi tą drogą, można potem animować przy wyświetlaniu.

Zapamiętaj dwa zdania: propsy schodzą w dół, zdarzenia idą w górę. Rodzic jest właścicielem danych, potomek prosi o ich zmianę. To właśnie czyni przepływ przewidywalnym, a źródło każdej zmiany zawsze możliwym do prześledzenia.

W Options API

Zdarzenia deklaruje się w opcji emits, a emituje przez this.$emit('add-to-cart', id). Rodzic nasłuchuje ich dokładnie tak samo.

ĆwiczenieDodaj zdarzenie remove-from-cart emitowane przez drugi przycisk i spraw, żeby rodzic usuwał z tablicy ostatni identyfikator.

Częste błędy

Nazwa zdarzenia w camelCase Atrybuty HTML ignorują wielkość liter: szablon spoza pliku .vue dostałby @addtocart i nasłuch nigdy by się nie odpalił. Używaj kebab-case.
Oczekiwanie, że zdarzenie przejdzie przez kilka poziomów Zdarzenie trafia wyłącznie do bezpośredniego rodzica. Powyżej dwóch poziomów przejdź na współdzielony stan z Pinią.
Zmiana stanu rodzica z poziomu potomka Potomek sygnalizuje, rodzic decyduje. To dzięki temu źródło zmian pozostaje możliwe do prześledzenia.
Newsletter

Nowe testy, poradniki i projekty — e-mailem.

Powtarzalne testy, wersjonowany kod, datowane wyniki. Nigdy spamu.