Przejdź bezpośrednio do treści

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:

Drzewo komponentów

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>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">Kliknąłeś mnie {{ count }} razy.</button>
</template>
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
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Kliknąłeś mnie {{ count }} razy.
    </button>`
}
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>
import ButtonCounter from './ButtonCounter.vue'

export default {
  components: {
    ButtonCounter
  }
}
</script>

<template>
  <h1>Oto komponent podrzędny!</h1>
  <ButtonCounter />
</template>

Aby udostępnić zaimportowany komponent w naszym szablonie, musimy zarejestrować go za pomocą opcji components. Komponent będzie dostępny jako tag, używając nazwy, pod którą został zarejestrowany.

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 opcji propsmakra defineProps:

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title']
}
</script>

<template>
  <h4>{{ title }}</h4>
</template>

Gdy wartość jest przekazywana do atrybutu prop, staje się ona właściwością w tym wystąpieniu komponentu. Wartość tej właściwości jest dostępna w szablonie i w kontekście this komponentu, tak jak każda inna właściwość komponentu.

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
export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, title: 'My journey with Vue' },
        { id: 2, title: 'Blogowanie z Vue"' },
        { id: 3, title: 'Dlaczego Vue jest takie fajne' }
      ]
    }
  }
}
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 właściwość danychodniesienie (ref) postFontSize :

js
data() {
  return {
    posts: [
      /* ... */
    ],
    postFontSize: 1
  }
}
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ą opcji emitsmakra defineEmits:

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title'],
  emits: ['enlarge-text']
}
</script>
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="currentTab"></component>
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.

Podstawy komponentówJest załadowany