Skip to content

Základy komponent

Komponenty nám umožňují rozdělit UI na nezávislé a znovupoužitelné části a přemýšlet o každé části samostatně. Je běžné, že je aplikace organizována do stromu vnořených komponent:

Strom komponent

Je to velmi podobné tomu, jak vnořujeme nativní HTML elementy, ale Vue implementuje svůj vlastní model komponent, který nám umožňuje zapouzdřit do každé komponenty její vlastní obsah a logiku. Vue také dobře funguje s nativními Web Components. Pokud vás zajímá vztah mezi Vue komponentami a Web Components, přečtěte si více zde.

Definice komponenty

Při použití build fáze obvykle definujeme každou Vue komponentu ve vyhrazeném souboru pomocí přípony .vue - známém jako Single-File komponenta (zkráceně SFC):

vue
<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">Klikli jste na mě {{ count }} krát.</button>
</template>
vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">Klikli jste na mě {{ count }} krát.</button>
</template>

Když nepoužíváte build fázi, lze Vue komponentu definovat jako prostý JavaScript objekt obsahující vlastnosti specifické pro Vue:

js
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Klikli jste na mě {{ count }} krát.
    </button>`
}
js
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      Klikli jste na mě {{ count }} krát.
    </button>`
  // může také adresovat in-DOM šablonu
  // template: '#my-template-element'
}

Šablona je zde vložena inline jako JavaScript string, který Vue za běhu zkompiluje. Můžete také použít ID selektor ukazující na element (obvykle nativní <template> elementy) – Vue použije jeho obsah jako zdroj pro šablonu.

Výše uvedený příklad definuje jednu komponentu a exportuje ji jako default export souboru .js. Můžete však použít pojmenované exporty (named exports) k exportu více komponent ze stejného souboru.

Použití komponenty

TIP

Ve zbytku tohoto průvodce budeme používat SFC syntaxi – koncepty týkající se komponent jsou stejné bez ohledu na to, zda build fázi používáte nebo ne.
Sekce Příklady ukazuje použití komponent v obou scénářích.

Abychom mohli použít komponentu potomka, musíme ji do komponenty rodiče importovat. Za předpokladu, že jsme naši komponentu „counter“ tlačítka umístili do souboru s názvem ButtonCounter.vue, bude tato komponenta vystavena jako default export souboru:

vue
<script>
import ButtonCounter from './ButtonCounter.vue'

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

<template>
  <h1>Zde je komponenta potomka!</h1>
  <ButtonCounter />
</template>

Abychom mohli importovanou komponentu vystavit pro naší šablonu, musíme ji zaregistrovat prostřednictvím možnosti components. Komponenta pak bude dostupná jako tag s názvem klíče, pod kterým je registrována.

vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Zde je komponenta potomka!</h1>
  <ButtonCounter />
</template>

S využitím <script setup> budou importované komponenty v šabloně dostupné automaticky.

Je také možné zaregistrovat komponentu globálně a zpřístupnit ji všem komponentám v dané aplikaci, aniž byste ji museli importovat. Klady a zápory globální vs. lokální registrace jsou rozebírány ve vyhrazené části Registrace komponent.

Komponenty lze použít opakovaně, kolikrát budete chtít:

template
<h1>Zde jsou komponenty potomků!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

Všimněte si, že při kliknutí na tlačítka si každé z nich zachovává svůj vlastní, samostatný count. Je to proto, že pokaždé, když komponentu použijete, vytvoří se nová instance.

V SFC se pro názvy tagů podřízených komponent doporučuje používat PascalCase, aby se odlišily od nativních HTML elementů. Přestože nativní názvy HTML elementů malá a velká písmena nerozlišují, Vue SFC je kompilovaný formát, takže v něm názvy rozlišující malá a velká písmena používat můžete. K uzavření tagu můžeme také použít />.

Pokud vaše šablony vytváříte přímo v DOM (např. jako obsah nativního elementu <template>), bude šablona při analýze HTML podléhat nativnímu chování prohlížeče. V takových případech budete muset pro názvy komponent použít kebab-case a explicitní uzavírací tagy:

template
<!-- pokud je šablona napsaná v DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

Pro více detailů se podívejte na upozornění na omezení při anlýze in-DOM šablon.

Předávání vlastností (props)

Pokud vytváříme blog, budeme pravděpodobně potřebovat komponentu představující příspěvek na blogu. Chceme, aby všechny příspěvky sdílely stejné vizuální rozvržení, ale s jiným obsahem. Taková komponenta nebude užitečná, pokud jí nebudete moci předat data, jako je název a obsah konkrétního příspěvku, který chceme zobrazit. Zde přicházejí na řadu vlastnosti (props).

Props jsou vlastní atributy, které můžete na komponentě zaregistrovat. Abychom naší komponentě předali název blogového příspěvku, musíme jej deklarovat v seznamu vlastností, které tato komponenta přijímá, pomocí možnosti propsmakra defineProps:

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

<template>
  <h4>{{ titulek }}</h4>
</template>

Když je hodnota předána pomocí prop atributu, stane se vlastností této instance komponenty. Hodnota této vlastnosti je přístupná v rámci šablony a v kontextu this komponenty, stejně jako jakákoli jiná její vlastnost.

vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['titulek'])
</script>

<template>
  <h4>{{ titulek }}</h4>
</template>

defineProps je makro překladače, které je dostupné pouze ve <script setup> a nemusí být explicitně importováno. Deklarované vlastnosti jsou automaticky zpřístupněny šabloně. defineProps také vrátí objekt, který obsahuje všechny vlastnosti předané komponentě, takže k nim můžeme v případě potřeby přistupovat v JavaScriptu:

js
const props = defineProps(['titulek'])
console.log(props.titulek)

Viz také: Typování vlastností komponenty

Pokud nepoužíváte <script setup>, props by měly být deklarovány pomocí možnosti props a props objekt předán funkci setup() jako první parametr:

js
export default {
  props: ['titulek'],
  setup(props) {
    console.log(props.titulek)
  }
}

Komponenta může mít tolik props, kolik chcete, a ve výchozím nastavení lze libovolné z nich předat libovolnou hodnotu.

Jakmile je vlastnost zaregistrována, můžete jí předávat data skrz vlastní atribut, například takto:

template
<BlogPost titulek="Moje cesta k Vue" />
<BlogPost titulek="Blogování s Vue" />
<BlogPost titulek="Proč je Vue tak zábavné" />

V typické aplikaci však pravděpodobně budete mít v komponentě rodiče pole příspěvků:

js
export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, titulek: 'Moje cesta k Vue' },
        { id: 2, titulek: 'Blogování s Vue' },
        { id: 3, titulek: 'Proč je Vue tak zábavné' }
      ]
    }
  }
}
js
const posts = ref([
  { id: 1, titulek: 'Moje cesta k Vue' },
  { id: 2, titulek: 'Blogování s Vue' },
  { id: 3, titulek: 'Proč je Vue tak zábavné' }
])

A potom pro každý z nich vykreslit jeho vlastní kompomentu pomocí v-for:

template
<BlogPost
  v-for="post in posts"
  :key="post.id"
  :titulek="post.titulek"
 />

Všimněte si, jak je k předávání dynamických prop hodnot použitá direktiva v-bind. To je užitečné zejména tehdy, když předem přesně nevíte, jaký obsah se chystáte vykreslit.

To je zatím vše, co o vlastnostech (props) potřebujete vědět. Poté, co si přečtete tuto stránku a budete se s jejím obsahem cítit seznámeni, však doporučujeme později se vrátit a přečíst si úplného průvodce pro Vlastnosti (Props).

Naslouchání událostem (events)

Jak vyvíjíme naši komponentu <BlogPost>, některé funkce mohou vyžadovat zpětnou komunikaci do komponenty rodiče. Můžeme se například rozhodnout zahrnout funkci usnadnění pro zvětšení textu blogových příspěvků, zatímco zbytek stránky ponecháme ve výchozí velikosti.

V komponentně rodiče můžeme tuto funkci podporovat přidáním proměnné postFontSize v možnosti dataref hodnoty postFontSize:

js
data() {
  return {
    posts: [
      /* ... */
    ],
    postFontSize: 1
  }
}
js
const posts = ref([
  /* ... */
])

const postFontSize = ref(1)

Která může být použita v šabloně k ovládání velikosti písma všech blogových příspěvků:

template
<div :style="{ fontSize: postFontSize + 'em' }">
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :titulek="post.titulek"
   />
</div>

Nyní pojďme přidat tlačítko do šablony <BlogPost> komponenty:

vue
<!-- BlogPost.vue, s vynecháním <script> -->
<template>
  <div class="blog-post">
    <h4>{{ titulek }}</h4>
    <button>Zvětšit text</button>
  </div>
</template>

Tlačítko zatím nic nedělá – chceme kliknutím na tlačítko sdělit komponentně rodiče, že má zvětšit text všech příspěvků. K vyřešení tohoto problému poskytují komponenty vlastní systém událostí (events). Rodič se může rozhodnout poslouchat libovolnou událost na instanci komponenty potomka pomocí v-on nebo @, stejně jako bychom to dělali s nativní událostí DOM:

template
<BlogPost
  ...
  @zvetsit-text="postFontSize += 0.1"
 />

Potom může komponenta potomka vyvolat událost sama na sobě voláním vestavěné metody $emit a předáním názvu události:

vue
<!-- BlogPost.vue, s vynecháním <script> -->
<template>
  <div class="blog-post">
    <h4>{{ titulek }}</h4>
    <button @click="$emit('zvetsit-text')">Zvětšit text</button>
  </div>
</template>

Díky event listeneru @zvetsit-text="postFontSize += 0.1" obdrží komponenta rodiče volání a provede aktualizaci hodnoty postFontSize.

Vysílané (emit) události můžeme nepovinně deklarovat s pomocí možnosti emitsmakra defineEmits:

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['titulek'],
  emits: ['zvetsit-text']
}
</script>
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['titulek'])
defineEmits(['zvetsit-text'])
</script>

To dokumentuje všechny události, které komponenta vysílá, a volitelně je validuje. Také to Vue umožňuje vyhnout se jejich implicitnímu použití jako nativních event listenerů na kořenovém prvku komponenty potomka.

Stejně jako defineProps, je i defineEmits použitelné pouze ve <script setup> a není třeba ho importovat. Vrací funkci emit, která je ekvivalentní metodě $emit. Může být použita k vyvolání událostí uvnitř <script setup> v komponentě, kde není $emit přímo dostupné:

vue
<script setup>
const emit = defineEmits(['zvetsit-text'])

emit('zvetsit-text')
</script>

Viz také: Typování emitovaných událostí komponenty

Pokud nepoužíváte <script setup>, můžete deklarovat emitované události prostřectvím možnosti emits. K funkci emit můžete přistuput jako k vlastnosti setup kontextu (předávaný do setup() jako druhý parametr):

js
export default {
  emits: ['zvetsit-text'],
  setup(props, ctx) {
    ctx.emit('zvetsit-text')
  }
}

To je zatím vše, co potřebujete vědět o vlastních událostech komponenty. Poté, co si přečtete tuto stránku a budete se s jejím obsahem cítit seznámeni, však doporučujeme později se vrátit a přečíst si úplného průvodce pro Události komponent (Events).

Distribuce obsahu pomocí slotů (slots)

Stejně jako u HTML elementů je často užitečné mít možnost předat obsah komponentě, jako je tato:

template
<AlertBox>
  Stala se chyba.
</AlertBox>

Což by mohlo vykreslit něco jako:

Toto je chyba pro testovací účely

Stala se chyba.

Toho lze dosáhnout použitím speciálního Vue elementu <slot>:

vue
<template>
  <div class="alert-box">
    <strong>Toto je chyba pro testovací účely</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

Jak vidíte výše, používáme <slot> jako zástupný symbol v místě, kde chceme umístit obsah - a to je vše. Máme hotovo!

To je zatím vše, co potřebujete vědět o slotech. Poté, co si přečtete tuto stránku a budete se s jejím obsahem cítit seznámeni, však doporučujeme později se vrátit a přečíst si úplného průvodce pro Sloty (Slots).

Dynamické komponenty

Někdy je užitečné mezi komponentami dynamicky přepínat, například v rozhraní s více taby:

Výše uvedené je možné s použitím Vue elementu <component> a jeho speciálního atributu is:

template
<!-- Komponenta se změní, když se změní `currentTab` -->
<component :is="currentTab"></component>
template
<!-- Komponenta se změní, když se změní `currentTab` -->
<component :is="tabs[currentTab]"></component>

V předchozím příkladu může hodnota předávaná do :is obsahovat:

  • string se jménem registrované komponenty, NEBO
  • vlastní importovaný objekt komponenty

Atribut is můžete také použít pro vytváření běžných HTML elementů.

Při přepínání mezi více komponentami pomocí <component :is="..."> bude komponenta odpojena (unmounted), když je z ní přepnuto jinam. Neaktivní komponenty můžete donutit, aby zůstaly „naživu“ pomocí vestavěné komponenty <KeepAlive>.

Omezení při parsování in-DOM šablon

Pokud píšete své Vue šablony přímo v DOM, Vue bude muset definici šablony (string template) z DOM načíst. To vede k určitým omezením kvůli chování prohlížečů při nativní analýze HTML.

TIP

Je třeba poznamenat, že níže popsaná omezení platí pouze v případě, že své šablony píšete přímo v DOM. NEPLATÍ, pokud používáte string templates z následujících zdrojů:

  • Single-File komponenty (SFC)
  • Inlined template strings (např. template: '...')
  • <script type="text/x-template">

Necitlivost na malá a velká písmena

HTML tagy a názvy atributů nerozlišují velká a malá písmena, takže prohlížeče budou všechna velká písmena interpretovat jako malá. To znamená, že když používáte in-DOM šablony, PascalCase názvy komponent a camelCased názvy vlastností (props) nebo názvy v-on událostí (events), musí všechny používat jejich ekvivalenty ve formátu kebab-case (oddělené pomlčkou):

js
// camelCase in JavaScript
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
template
<!-- kebab-case in HTML -->
<blog-post post-title="Ahoj!" @update-post="onUpdatePost"></blog-post>

Samouzavírací tagy

V předchozích ukázkách kódu jsme pro komponenty používali samouzavírací tagy:

template
<MyComponent />

Je to proto, že parser Vue šablon respektuje /> jako indikaci ukončení jakéhokoli tagu, bez ohledu na jeho typ.

V in-DOM šablonách však musíme vždy zahrnout explicitní uzavírací tagy:

template
<my-component></my-component>

Je to proto, že specifikace HTML umožňuje pouze několika konkrétním prvkům vynechat uzavírací tag, nejběžnější jsou <input> a <img>. Pokud u všech ostatních prvků uzavírací tag vynecháte, nativní HTML parser si bude myslet, že jste úvodní tag nikdy neukončili. Například následující kus kódu:

template
<my-component /> <!-- zde chceme tag ukončit... -->
<span>ahoj</span>

Bude vyhodnocen jako:

template
<my-component>
  <span>ahoj</span>
</my-component> <!-- prohlížeč ho však ukončí až tady -->

Omezení umístění elementů

Některé HTML elementy, jako jsou <ul>, <ol>, <table> a <select> mají omezení ohledně toho, jaké prvky se v nich mohou objevit, a některé elementy jako <li>, <tr> a <option> se mohou objevit pouze uvnitř určitých jiných elementů.

To povede k problémům při používání komponent s elementy, které mají taková omezení. Například:

template
<table>
  <blog-post-row></blog-post-row>
</table>

Naše komponenta <blog-post-row> bude vytažena (hoisted) jako neplatný obsah, což v případném vykresleném výstupu způsobí chyby. Toto můžeme obejít s použitím speciálního atributu is:

template
<table>
  <tr is="vue:blog-post-row"></tr>
</table>

TIP

Při použití na nativní HTML elementy musí být hodnota is uvedena předponou vue:, aby mohla být interpretována jako Vue komponenta. Je to nutné, aby nedošlo k záměně s nativními custom built-in elementy.

To je vše, co zatím potřebujete vědět o omezeních při parsování in-DOM šablon ‑ a vlastně konec Základů Vue. Gratulujeme! Je stále co se učit, ale nejprve doporučujeme, abyste si udělali přestávku a sami si s Vue hráli – vytvořit něco zábavného, nebo se podívat na některé Příklady, pokud jste tak ještě neučinili.

Jakmile si budete jisti znalostmi, které jste právě nabrali, pokračujte v průvodci, abyste se o komponentách dozvěděli více do hloubky.

Základy komponent has loaded