Podstawy komponentów
Komponenty pozwalają nam podzielić interfejs użytkownika na niezależne części wielokrotnego użytku oraz myśleć o każdej z nich w izolacji. Często aplikacja jest organizowana w strukturę komponentów zagnieżdżonych w drzewie:
Jest to bardzo podobne do sposobu, w jaki zagnieżdżamy natywne elementy HTML, ale Vue implementuje swój własny model komponentów, który pozwala hermetyzować niestandardową zawartość i logikę w każdym komponencie. Vue dobrze współpracuje również z natywnymi komponentami sieciowymi (Web Components). Jeśli jesteś ciekawy relacji między komponentami Vue a natywnymi komponentami sieciowymi, przeczytaj więcej tutaj.
Definiowanie komponentu
Podczas korzystania z procesu budowania, zazwyczaj definiujemy każdy komponent Vue w dedykowanym pliku z rozszerzeniem .vue
- znanym jako jednoplikowy komponent (Single-File Component) (w skrócie SFC):
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">Kliknąłeś mnie {{ count }} razy.</button>
</template>
Kiedy nie używamy procesu budowania, komponent Vue może być zdefiniowany jako zwykły obiekt JavaScript zawierający opcje specyficzne dla Vue:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
Kliknąłeś mnie {{ count }} razy.
</button>`
// Można również używać szablonów w DOM:
// template: '#my-template-element'
}
Szablon jest tutaj wstawiony bezpośrednio jako ciąg znaków JavaScript, który Vue skompiluje na bieżąco. Można również używać selektora ID wskazującego na element (zwykle natywne elementy <template>
) - Vue użyje jego zawartości jako źródła szablonu.
Powyższy przykład definiuje pojedynczy komponent i eksportuje go jako domyślny eksport pliku .js
, ale można używać eksportów nazwanych, aby eksportować wiele komponentów z tego samego pliku.
Używanie komponentu
TIP
Będziemy używać składni SFC przez resztę tego przewodnika - koncepcje związane z komponentami są takie same, niezależnie od tego, czy używasz procesu budowania, czy nie. Sekcja przykłady pokazuje użycie komponentów w obu scenariuszach.
Aby użyć komponentu podrzędnego, musimy go zaimportować do komponentu nadrzędnego. Zakładając, że nasz komponent licznika umieściliśmy w pliku o nazwie ButtonCounter.vue
, komponent będzie dostępny jako domyślny eksport tego pliku:
vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Oto komponent podrzędny!</h1>
<ButtonCounter />
</template>
W przypadku <script setup>
, zaimportowane komponenty są automatycznie dostępne w szablonie.
Możliwe jest również globalne zarejestrowanie komponentu, co sprawia, że jest on dostępny dla wszystkich komponentów w danej aplikacji bez konieczności importowania go. Wady i zalety rejestracji globalnej vs lokalnej są omówione w dedykowanej sekcji rejestracja komponentów.
Komponenty mogą być używane tyle razy, ile chcesz:
template
<h1>Oto wiele komponentów podrzędnych!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />
Zauważ, że podczas klikania przycisków, każdy z nich utrzymuje swój własny, osobny count
. Dzieje się tak, ponieważ za każdym razem, gdy używasz komponentu, tworzona jest nowa instancja tego komponentu.
W SFC zaleca się używanie nazw tagów w PascalCase
dla komponentów podrzędnych, aby odróżnić je od natywnych elementów HTML. Chociaż nazwy tagów w HTML są niewrażliwe na wielkość liter, SFC Vue to format skompilowany, więc możemy używać nazw tagów rozróżniających wielkość liter. Możemy również używać />
do zamykania tagu.
Jeśli tworzysz swoje szablony bezpośrednio w DOM (np. jako zawartość natywnego elementu <template>
), szablon będzie podlegał natywnemu zachowaniu parsowania HTML przeglądarki. W takich przypadkach będziesz musiał użyć kebab-case
i jawnych tagów zamykających dla komponentów:
template
<!-- jeśli ten szablon jest napisany w DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
Zobacz zagadnienia związane z analizą szablonów w DOM po więcej szczegółów.
Przekazywanie właściwości (props)
Jeśli budujemy bloga, prawdopodobnie będziemy potrzebować komponentu reprezentującego post. Chcemy, aby wszystkie posty miały ten sam układ wizualny, ale różną zawartość. Taki komponent nie będzie użyteczny, jeśli nie będziesz mógł przekazać do niego danych, takich jak tytuł i treść konkretnego posta, który chcemy wyświetlić. Właśnie do tego służą właściwości (props).
Właściwości (props) to niestandardowe atrybuty, które można zarejestrować w komponencie. Aby przekazać tytuł do naszego komponentu posta blogowego, musimy zadeklarować go na liście właściwości, które ten komponent akceptuje, używając makra defineProps
:
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
defineProps
to makro kompilacyjne dostępne tylko w obrębie <script setup>
nie wymagające bezpośredniego importu. Zadeklarowane właściwości są automatycznie dostępne w szablonie. defineProps
zwraca również obiekt zawierający wszystkie właściwości przekazane do komponentu, dzięki czemu możemy uzyskać do nich dostęp w JavaScript, jeśli to potrzebne:
js
const props = defineProps(['title'])
console.log(props.title)
Zobacz także: Typowanie właściwości komponentu
Jeśli nie używasz <script setup>
, właściwości (props) powinny być deklarowane przy użyciu opcji props
, a obiekt props zostanie przekazany do funkcji setup()
jako pierwszy argument:
js
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}
Komponent może mieć dowolną liczbę właściwości (props), a domyślnie każda wartość może być przekazana do każdej właściwości.
Po zarejestrowaniu właściwości można przekazać do niej dane jako atrybut niestandardowy:
template
<BlogPost title="Moja przygoda z Vue" />
<BlogPost title="Blogowanie z Vue" />
<BlogPost title="Dlaczego Vue jest takie fajne" />
Jednak w typowej aplikacji w komponencie nadrzędnym prawdopodobnie będzie dostępna tablica postów:
js
const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogowanie z Vue"' },
{ id: 3, title: 'Dlaczego Vue jest takie fajne' }
])
Następnie należy wyrenderować komponent dla każdego elementu, używając v-for
:
template
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
Zauważ, że do przekazywania dynamicznych wartości do właściwości komponentu użyto składni v-bind
(:title="post.title"
). Jest to szczególnie przydatne, gdy nie wiadomo z góry, jaka zawartość zostanie wyrenderowana.
To wszystko, co na razie musisz wiedzieć o właściwościach (props), ale gdy skończysz czytać tę stronę i poczujesz się komfortowo z jej treścią, zalecamy powrót później, aby przeczytać cały poradnik dotyczący właściwości (props).
Nasłuchiwanie wydarzeń
Podczas tworzenia komponentu <BlogPost>
niektóre funkcjonalności mogą wymagać komunikacji z komponentem nadrzędnym. Przykładowo można dodać funkcję zwiększania rozmiaru tekstu w postach, zachowując resztę strony w domyślnym rozmiarze.
W komponencie nadrzędnym można obsłużyć tę funkcjonalność, dodając odniesienie (ref) postFontSize
:
js
const posts = ref([
/* ... */
])
const postFontSize = ref(1)
Którego można użyć w szablonie do kontrolowania rozmiaru czcionki wszystkich wpisów w blogu:
template
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</div>
Teraz dodajmy przycisk do szablonu komponentu <BlogPost>
:
vue
<!-- BlogPost.vue, pomijając <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button>Powiększ tekst</button>
</div>
</template>
Przycisk nie wykonuje jeszcze żadnej akcji - chcemy, aby kliknięcie przycisku komunikowało komponentowi rodzica, że powinien zwiększyć rozmiar tekstu wszystkich postów. Aby rozwiązać ten problem, komponenty zapewniają niestandardowy system wydarzeń. Rodzic może wybrać, czy chce nasłuchiwać dowolnego wydarzenia na instancji komponentu potomnego za pomocą v-on
lub @
, tak jak zrobilibyśmy to w przypadku natywnego wydarzenia DOM:
template
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
Następnie komponent podrzędny może wyemitować wydarzenie na sobie samym, wywołując wbudowaną metodę $emit
, przekazując nazwę wydarzenia:
vue
<!-- BlogPost.vue, pomijając <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Powiększ tekst</button>
</div>
</template>
Dzięki nasłuchiwaczowi @enlarge-text="postFontSize += 0.1"
element nadrzędny odbierze wydarzenie i zaktualizuje wartość postFontSize
.
Opcjonalnie możemy zadeklarować emitowane wydarzenia za pomocą makra defineEmits
:
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>
Dokumentuje to wszystkie wydarzenia emitowane przez komponent i opcjonalnie waliduje je. Pozwala to również Vue uniknąć niejawnego stosowania ich jako natywnych nasłuchiwaczy do głównego elementu komponentu podrzędnego.
Podobnie jak defineProps
, defineEmits
można używać tylko w <script setup>
i nie trzeba go importować. Zwraca funkcję emit
, która jest równoważna metodzie $emit
. Można jej używać do emitowania wydarzeń w sekcji <script setup>
komponentu, gdzie $emit
nie jest bezpośrednio dostępny:
vue
<script setup>
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>
Zobacz także: Typowanie emitów komponentu
Jeśli nie używasz <script setup>
, możesz zadeklarować emitowane wydarzenia za pomocą opcji emits
. Możesz uzyskać dostęp do funkcji emit
jako właściwości kontekstu konfiguracji (przekazanej do setup()
jako drugi argument):
js
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}
To wszystko, co na razie musisz wiedzieć o niestandardowych wydarzeniach komponentów. Jednak gdy skończysz czytać tę stronę i poczujesz, że jej treść jest dla Ciebie interesująca, zalecamy powrót później, aby przeczytać cały przewodnik na temat niestandardowych wydarzeń.
Dystrybucja treści za pomocą slotów
Podobnie jak w przypadku elementów HTML, często zachodzi potrzeba przekazania treści do komponentu:
template
<AlertBox>
Coś złego się stało.
</AlertBox>
Co może wyrenderować coś takiego:
To jest błąd demonstracyjny
Coś złego się stało.
Aby to osiągnąć, w Vue używa się niestandardowego elementu <slot>
:
vue
<!-- AlertBox.vue -->
<template>
<div class="alert-box">
<strong>To jest błąd demonstracyjny</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
Jak widać powyżej, używamy <slot>
jako symbolu zastępczego, w którym chcemy umieścić treść – i to wszystko. Gotowe!
To na razie wszystko, co musisz wiedzieć o slotach, ale gdy już skończysz czytać tę stronę i poczujesz się komfortowo z jej treścią, zalecamy powrót później, aby przeczytać cały poradnik dotyczący slotów.
Komponenty dynamiczne
Czasami przydatne jest dynamiczne przełączanie się między komponentami, na przykład w interfejsie z kartami:
Powyższe jest możliwe dzięki elementowi Vue <component>
ze specjalnym atrybutem is
:
template
<!-- komponent zmienia się, gdy zmienia się bieżąca karta -->
<component :is="tabs[currentTab]"></component>
W powyższym przykładzie wartość przekazana do :is
może zawierać:
- nazwę zarejestrowanego komponentu (string) LUB
- rzeczywisty zaimportowany obiekt komponentu
Można również użyć atrybutu is
do utworzenia zwykłych elementów HTML.
Podczas przełączania między wieloma komponentami za pomocą <component :is="...">
, komponent zostanie odmontowany, gdy zostanie odłączony. Możemy wymusić, aby nieaktywne komponenty pozostały „aktywne” za pomocą wbudowanego komponentu <KeepAlive>
.
Ostrzeżenia dotyczące parsowania szablonów w DOM
Jeśli piszesz swoje szablony Vue bezpośrednio w DOM, Vue będzie musiał pobrać ciąg szablonu z DOM. Prowadzi to do pewnych ograniczeń wynikających z natywnego sposobu parsowania HTML przez przeglądarki.
TIP
Należy zauważyć, że ograniczenia omówione poniżej mają zastosowanie tylko wtedy, gdy piszesz swoje szablony bezpośrednio w DOM. NIE mają zastosowania, jeśli używasz ciągów szablonów z następujących źródeł:
- komponenty jednoplikowe (Single-File Components SFC)
- wbudowane ciągi szablonów (e.g.
template: '...'
) <script type="text/x-template">
Niewrażliwość na wielkość liter
Znaczniki HTML i nazwy atrybutów nie uwzględniają wielkości liter, więc przeglądarki zinterpretują wszystkie wielkie litery jako małe. Oznacza to, że gdy używasz szablonów w DOM, nazwy komponentów PascalCase i nazwy właściwości camelCased lub nazwy zdarzeń v-on
muszą używać swoich odpowiedników w formacie kebab-cased (rozdzielonych myślnikami):
js
// camelCase w JavaScript
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}
template
<!-- kebab-case w HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>
Tagi samozamykające się
W poprzednich przykładach kodu używaliśmy samozamykających się znaczników dla komponentów:
template
<MyComponent />
Dzieje się tak, ponieważ parser szablonów Vue respektuje />
jako wskazanie zakończenia dowolnego znacznika, niezależnie od jego typu.
W szablonach in-DOM musimy jednak zawsze uwzględniać wyraźne znaczniki zamykające:
template
<my-component></my-component>
Dzieje się tak, ponieważ specyfikacja HTML zezwala tylko kilku konkretnym elementom na pominięcie znaczników zamykających, z których najczęstszymi są <input>
i <img>
. W przypadku wszystkich innych elementów, jeśli pominiesz znacznik zamykający, natywny parser HTML uzna, że nigdy nie zakończyłeś znacznika otwierającego. Przykładem jest poniższy fragment kodu:
template
<my-component /> <!-- zamierzamy zamknąć tutaj tag... -->
<span>cześć</span>
will be parsed as:
template
<my-component>
<span>cześć</span>
</my-component> <!-- ale przeglądarka zamknie go tutaj. -->
Ograniczenia rozmieszczenia elementów
Niektóre elementy HTML, takie jak <ul>
, <ol>
, <table>
i <select>
mają ograniczenia dotyczące tego, jakie elementy mogą się w nich pojawiać, a niektóre elementy, takie jak <li>
, <tr>
i <option>
, mogą pojawiać się tylko w niektórych z innych elementów.
To prowadzi do problemów podczas używania komponentów z elementami, które mają takie ograniczenia. Na przykład:
template
<table>
<blog-post-row></blog-post-row>
</table>
Niestandardowy komponent <blog-post-row>
zostanie wyniesiony jako nieprawidłowa zawartość, powodując błędy w ostatecznym wyniku renderowania. Możemy użyć specjalnego atrybutu is
jako obejścia:
template
<table>
<tr is="vue:blog-post-row"></tr>
</table>
TIP
W przypadku użycia w natywnych elementach HTML wartość is
musi być poprzedzona prefiksem vue:
, aby mogła zostać zinterpretowana jako komponent Vue. Jest to wymagane, aby uniknąć pomyłki z natywnymi customowymi wbudowanymi elementami.
To wszystko, co musisz na razie wiedzieć na temat zastrzeżeń dotyczących analizy szablonów w DOM - i tak naprawdę koniec Essentials Vue. Gratulacje! Jest jeszcze wiele do nauczenia, ale najpierw zalecamy zrobienie sobie przerwy, aby samemu pobawić się Vue - zbuduj coś fajnego lub sprawdź niektóre z przykładów, jeśli jeszcze tego nie zrobiłeś.
Gdy poczujesz się komfortowo z wiedzą, którą właśnie przyswoiłeś, przejdź do przewodnika, aby dowiedzieć się więcej o komponentach dogłębnie.