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 analý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:

BlogPost.vue
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.

BlogPost.vue
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í komponentu 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á zkrácená v-bind syntaxe (:titulek="post.titulek"). 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 komponentě 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:

BlogPost.vue
vue
<!-- sekce <script> vynechána -->
<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 komponentě 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:

BlogPost.vue
vue
<!-- sekce <script> vynechána -->
<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:

BlogPost.vue
vue
<script>
export default {
  props: ['titulek'],
  emits: ['zvetsit-text']
}
</script>
BlogPost.vue
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 root elementu 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řednictvím možnosti emits. K funkci emit můžete přistupit 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>:

AlertBox.vue
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