Kurs Vue.js · Moduł 7: Props i zdarzenia
Fallthrough Attributes
W tej lekcji5
Sygnały telemetryczne, które nie są zdefiniowane jako props, automatycznie "przenikają" do głównego elementu komponentu - jak dane z czujników do centralnego procesora NOVA LAB. Ten mechanizm nazywa się fallthrough attributes i jest ważnym aspektem tworzenia elastycznych komponentów.
Automatyczne Dziedziczenie
Atrybuty przekazane do komponentu, które NIE są zadeklarowane jako props ani emity, automatycznie trafiają do root elementu szablonu:
1<!-- Rodzic -->
2<CustomInput class="special" data-testid="email" />
3
4<!-- CustomInput.vue (root = input) -->
5<template>
6 <input type="text" />
7</template>
8
9<!-- Wynik w DOM -->
10<input type="text" class="special" data-testid="email" />Dotyczy to: class, style, id, data-*, event listenerów i innych atrybutów HTML. To zachowanie jest bardzo przydatne - pozwala użytkownikowi komponentu dodawać klasy CSS, style inline czy atrybuty dostępności bez konieczności definiowania ich jako props.
Łączenie klas i stylów
Jeśli root element komponentu ma już własne class lub style, Vue automatycznie je łączy (merge) z atrybutami z rodzica:
1<!-- Rodzic -->
2<StatusPanel class="highlighted" style="margin-top: 10px" />
3
4<!-- StatusPanel.vue -->
5<template>
6 <div class="panel" style="padding: 1rem">...</div>
7</template>
8
9<!-- Wynik w DOM - klasy i style połączone -->
10<div class="panel highlighted" style="padding: 1rem; margin-top: 10px">...</div>Dziedziczenie event listenerów
Listenery zdarzeń również przechodzą. Jeśli rodzic przekaże @click, zostanie on dodany do root elementu:
1<!-- Rodzic -->
2<MarsButton @click="handleClick" />
3
4<!-- MarsButton.vue -->
5<template>
6 <button @click="logInternal">Kliknij</button>
7</template>W tym przypadku kliknięcie przycisku wywoła oba handlery - najpierw logInternal z komponentu, potem handleClick z rodzica.
Wyłączenie Dziedziczenia
Gdy chcesz kontrolować dokładnie, gdzie trafiają atrybuty (np. chcesz je przypisać do wewnętrznego elementu zamiast root), użyj inheritAttrs: false:
1<script setup>
2defineOptions({ inheritAttrs: false })
3</script>
4
5<template>
6 <div class="wrapper">
7 <!-- Ręczne przypisanie do konkretnego elementu -->
8 <input v-bind="$attrs" type="text" />
9 </div>
10</template>To jest częsty wzorzec w komponentach formularzy - wrapper <div> służy do stylowania, a $attrs trafiają bezpośrednio na <input>, gdzie są najbardziej użyteczne (np. placeholder, disabled, aria-label).
useAttrs()
Dostęp do atrybutów w <script setup> uzyskujesz przez composable useAttrs():
1<script setup>
2import { useAttrs } from 'vue'
3
4const attrs = useAttrs()
5
6console.log(attrs.class) // "special"
7console.log(attrs['data-testid']) // "email"
8console.log(attrs.style) // obiekt stylów
9</script>Uwaga: useAttrs() NIE jest reaktywne - jeśli potrzebujesz reagować na zmiany atrybutów, użyj $attrs bezpośrednio w szablonie lub onUpdated hook.
Multi-root Components
Gdy komponent ma wiele root elementów (fragments), Vue nie wie, do którego przypisać fallthrough attributes. Musisz jawnie przypisać $attrs do wybranego elementu:
1<script setup>
2defineOptions({ inheritAttrs: false })
3</script>
4
5<template>
6 <header>Header</header>
7 <main v-bind="$attrs">Content</main>
8 <footer>Footer</footer>
9</template>Bez inheritAttrs: false i jawnego v-bind="$attrs", Vue wyświetli ostrzeżenie w konsoli. To wymaga świadomej decyzji - który element ma "dziedziczyć" atrybuty z rodzica.
Praktyczny przykład - komponent panelu NOVA LAB
Połączmy wszystkie koncepcje w jednym przykładzie:
1<script setup>
2import { useAttrs } from 'vue'
3
4defineOptions({ inheritAttrs: false })
5
6defineProps({ title: String })
7
8const attrs = useAttrs()
9const hasCustomClass = !!attrs.class
10</script>
11
12<template>
13 <section class="nova-panel">
14 <h3>{{ title }}</h3>
15 <div v-bind="$attrs" class="panel-content">
16 <slot />
17 </div>
18 <small v-if="hasCustomClass">Custom styling applied</small>
19 </section>
20</template>Dzięki temu wzorcowi rodzic może przekazać class, style, id i inne atrybuty, które trafią dokładnie na panel-content, a nie na zewnętrzną <section>.
Kod do tej lekcji: App.vue
1<script setup>
2import CustomInput from './CustomInput.vue'
3import MultiRoot from './MultiRoot.vue'
4</script>
5
6<template>
7 <div class="nova-lab">
8 <h1>Fallthrough Attributes</h1>
9
10 <h2>Automatyczne dziedziczenie:</h2>
11 <CustomInput
12 class="special-input"
13 data-testid="mars-input"
14 placeholder="Wpisz tutaj..."
15 />
16
17 <h2>inheritAttrs: false + ręczne wiązanie:</h2>
18 <MultiRoot
19 class="highlighted"
20 id="main-section"
21 data-role="content"
22 />
23 </div>
24</template>
25
26<style scoped>
27.nova-lab {
28 background: #0a0e27;
29 color: #00ff88;
30 min-height: 100vh;
31 padding: 20px;
32 font-family: monospace;
33}
34h1 { color: #00ff88; margin-bottom: 20px; }
35h2 { color: #00b4d8; margin: 15px 0 10px; }
36.special-input { border-color: #ff9900 !important; }
37.highlighted { background: rgba(0, 255, 136, 0.15) !important; }
38</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. Co to są fallthrough attributes w Vue?
Zadania praktyczne w grze
- Układanie w pionie
Uporządkuj kroki ręcznego wiązania attrs:
- Edytor kodu
Wyłącz inheritAttrs i ręcznie bind attrs do konkretnego elementu input.
- Klikanie w kolejności
Ułóż kod dostępu do attrs w script setup:
- Układanie w poziomie
Ułóż ręczne wiązanie attrs w template:
- Edytor kodu
Stwórz komponent z header, main i footer, przypisując $attrs tylko do main.