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

Fallthrough Attributes

3 min czytania
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. 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.

Przydatne artykuły