Skip to content

Grundlagen zu Komponenten

Komponenten ermöglichen es, das User Interface in unabhängige und wiederverwendbare Teile aufzuteilen und jedes einzelne Teil isoliert zu konzipieren. Üblicherweise wird eine Applikation als Baum verschachtelter Komponenten organisiert:

Komponentenbaum

Dies ist der Art, wie native HTML-Elemente ineinander verschachtelt werden, sehr ähnlich, wobei Vue sein eigenens Komponentenmodell nutzt. Dieses Modell ermöhlicht es, den Inhalt und die Funktionalität - also die Anwendungslogik - einer selbsterstellten Komponete zu kapseln. Vue harmoniert dabei gut mit nativen Komponenten (Web Components). Weiterführende Informationen zum Gemeinsamkeiten und Unterschieden von Komponenten in Vue und nativen Komponenten können Sie hier finden.

Komponenten definieren

Wird die Applikation mit einem Build Tool erstellt, definieren wir eine Vue-Komponente üblicherweise in einer eigenen Datei, die mit Dateiendung .vue gespeichert wird. Diese Komponente wird daher als Single-File Component bezeichnet (oder oft auch als SFC abgekürzt):

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

<template>
  <button @click="count++">Du hast mich {{ Anzahl }} Mal geklickt.</button>
</template>
vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">Du hast mich {{ Anzahl }} Mal geklickt.</button>
</template>

Wird kein Build Tool verwendet, kann eine Vue-Komponente als normales JavaScript-Objekt erstellt werden. Dieses Objekt enthält dabei einige Vue-spezifische Optionen:

js
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Du hast mich {{ Anzahl }} Mal geklickt.
    </button>`
}
js
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
  // or `template: '#my-template-element'`
}

Das Template ist hierbei ein Inline-JavaScript-String. Vue wird diesen String zu Laufzeit kompilieren. Es kann auch ein ID-Selektor verwendet werden, der auf ein DOM-Element verweist (üblicherweise das native <template> Element) - Vue nutzt dann dessen INhalt als Quelle für das Vue-Template.

Das Beispiel definiert eine einzelne Komponente und exportiert diese als Standardexport in einer .js-Datei. Es können aber auch benannte Exporte verwendet werden, um mehrere Komponenten aus derselben Datei zu exportieren.

Komponenten nutzen

TIP

In der Dokumentation werden wir die SFC-Syntax verwenden. Die eingesetzen Konzepte sind unabhängig von der Nutzung eines Build-Tools identisch. Im Bereich Beispiele finden sich Beispiele für die Nutzung von Komponenten in beiden Szenarien.

Um eine untergeordnete Komponente zu verwenden, müssen wir sie in die übergeordnete Komponente importieren. Angenommen, wir haben unsere Zählerkomponente in einer Datei mit dem Namen "ButtonCounter.vue" platziert, wird die Komponente als Standard-Export der Datei angezeigt:

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

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

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

Um die importierte Komponente für unsere Vorlage freizugeben, müssen wir registrieren mit der Option components. Die Komponente ist dann als Tag mit dem Schlüssel, unter dem sie registriert ist, verfügbar.

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

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

Mit <script setup> werden importierte Komponenten automatisch in der Vorlage verfügbar gemacht.

Es ist auch möglich, eine Komponente global zu registrieren, so dass sie für alle Komponenten in einer bestimmten Anwendung verfügbar ist, ohne dass sie importiert werden muss. Die Vor- und Nachteile der globalen gegenüber der lokalen Registrierung werden im Abschnitt Komponentenregistrierung diskutiert.

Die Komponenten können beliebig oft wiederverwendet werden:

template
<h1>Here are many child components!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

Beachten Sie, dass beim Anklicken der Schaltflächen jede ihre eigene, separate "Anzahl" beibehält. Das liegt daran, dass jedes Mal, wenn Sie eine Komponente verwenden, eine neue Instanz der Komponente erstellt wird.

In SFCs wird empfohlen, Tag-Namen für untergeordnete Komponenten in PascalCase zu verwenden, um sie von nativen HTML-Elementen zu unterscheiden. Obwohl native HTML-Tag-Namen Groß- und Kleinschreibung nicht berücksichtigen, ist Vue SFC ein kompiliertes Format, so dass wir in der Lage sind, Tag-Namen mit Groß- und Kleinschreibung zu verwenden. Wir sind auch in der Lage, /> zu verwenden, um ein Tag zu schließen.

Wenn Sie Ihre Templates direkt in einem DOM erstellen (z.B. als Inhalt eines nativen <Template>-Elements), wird das Template dem nativen HTML-Parsing-Verhalten des Browsers unterliegen. In solchen Fällen müssen Sie kebab-case und explizite schließende Tags für Komponenten verwenden:

template
<!-- if this template is written in the DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

Siehe DOM template parsing caveats für weitere Details.

Passende Requisiten

Wenn wir einen Blog erstellen, benötigen wir wahrscheinlich eine Komponente, die einen Blogbeitrag darstellt. Wir möchten, dass alle Blogeinträge das gleiche visuelle Layout haben, aber mit unterschiedlichem Inhalt. Eine solche Komponente ist nur dann nützlich, wenn Sie ihr Daten übergeben können, z. B. den Titel und den Inhalt des bestimmten Beitrags, den wir anzeigen möchten. An dieser Stelle kommen Props ins Spiel.

Props sind benutzerdefinierte Attribute, die Sie für eine Komponente registrieren können. Um einen Titel an unsere Blogpost-Komponente zu übergeben, müssen wir ihn in der Liste der Requisiten, die diese Komponente akzeptiert, deklarieren, indem wir die props OptiondefineProps Makro:

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

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

Wenn ein Wert an ein prop-Attribut übergeben wird, wird er zu einer Eigenschaft dieser Komponenteninstanz. Auf den Wert dieser Eigenschaft kann innerhalb der Vorlage und im Kontext "this" der Komponente zugegriffen werden, genau wie auf jede andere Komponenteneigenschaft.

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

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

defineProps ist ein Makro zur Kompilierzeit, das nur innerhalb von <script setup> verfügbar ist und nicht explizit importiert werden muss. Deklarierte Requisiten werden automatisch in der Vorlage angezeigt. defineProps gibt auch ein Objekt zurück, das alle an die Komponente übergebenen Requisiten enthält, so dass wir bei Bedarf in JavaScript darauf zugreifen können:

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

Siehe auch: Typing Component Props

Wenn Sie nicht <script setup> verwenden, sollten props mit der Option props deklariert werden, und das props-Objekt wird an setup() als erstes Argument übergeben:

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

Eine Komponente kann so viele Requisiten haben, wie Sie möchten, und standardmäßig kann jeder Wert an jede Requisite übergeben werden.

Sobald eine Requisite registriert ist, können Sie ihr Daten als benutzerdefiniertes Attribut übergeben, etwa so:

template
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

In einer typischen Anwendung werden Sie jedoch wahrscheinlich ein Array von Beiträgen in Ihrer übergeordneten Komponente haben:

js
export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, title: 'Meine Reise mit Vue' },
        { id: 2, title: 'Bloggen mit Vue' },
        { id: 3, title: 'Warum Vue so viel Spaß macht' }
      ]
    }
  }
}
js
const posts = ref([
  { id: 1, title: 'My journey with Vue' },
  { id: 2, title: 'Blogging with Vue' },
  { id: 3, title: 'Why Vue is so fun' }
])

Dann wollen Sie eine Komponente für jede, mit v-for zu rendern:

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

Beachten Sie, wie v-bind verwendet wird, um dynamische Prop-Werte zu übergeben. Dies ist besonders nützlich, wenn Sie den genauen Inhalt, den Sie rendern werden, nicht im Voraus kennen.

Das ist alles, was Sie im Moment über Requisiten wissen müssen. Wenn Sie diese Seite gelesen haben und mit ihrem Inhalt vertraut sind, empfehlen wir Ihnen, später wiederzukommen, um den vollständigen Leitfaden zu Props zu lesen.

Anhören von Veranstaltungen

Bei der Entwicklung unserer <BlogPost>-Komponente kann es erforderlich sein, dass einige Funktionen mit der übergeordneten Komponente rückgekoppelt werden müssen. Wir könnten zum Beispiel beschließen, eine Funktion für die Barrierefreiheit einzubauen, um den Text von Blogeinträgen zu vergrößern, während der Rest der Seite in seiner Standardgröße bleibt.

Im Elternteil können wir diese Funktion unterstützen, indem wir eine postFontSize data propertyref:

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

const postFontSize = ref(1)

Which can be used in the template to control the font size of all blog posts:

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

Now let's add a button to the <BlogPost> component's template:

vue
<!-- BlogPost.vue, omitting <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

The button doesn't do anything yet - we want clicking the button to communicate to the parent that it should enlarge the text of all posts. To solve this problem, components provide a custom events system. The parent can choose to listen to any event on the child component instance with v-on or @, just as we would with a native DOM event:

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

Then the child component can emit an event on itself by calling the built-in $emit method, passing the name of the event:

vue
<!-- BlogPost.vue, omitting <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

Thanks to the @enlarge-text="postFontSize += 0.1" listener, the parent will receive the event and update the value of postFontSize.

We can optionally declare emitted events using the emits optiondefineEmits macro:

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

This documents all the events that a component emits and optionally validates them. It also allows Vue to avoid implicitly applying them as native listeners to the child component's root element.

Similar to defineProps, defineEmits is only usable in <script setup> and doesn't need to be imported. It returns an emit function that is equivalent to the $emit method. It can be used to emit events in the <script setup> section of a component, where $emit isn't directly accessible:

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

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

See also: Typing Component Emits

If you are not using <script setup>, you can declare emitted events using the emits option. You can access the emit function as a property of the setup context (passed to setup() as the second argument):

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

That's all you need to know about custom component events for now, but once you've finished reading this page and feel comfortable with its content, we recommend coming back later to read the full guide on Custom Events.

Content Distribution with Slots

Just like with HTML elements, it's often useful to be able to pass content to a component, like this:

template
<AlertBox>
  Something bad happened.
</AlertBox>

Which might render something like:

This is an Error for Demo Purposes

Something bad happened.

This can be achieved using Vue's custom <slot> element:

vue
<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

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

As you'll see above, we use the <slot> as a placeholder where we want the content to go – and that's it. We're done!

That's all you need to know about slots for now, but once you've finished reading this page and feel comfortable with its content, we recommend coming back later to read the full guide on Slots.

Dynamic Components

Sometimes, it's useful to dynamically switch between components, like in a tabbed interface:

The above is made possible by Vue's <component> element with the special is attribute:

template
<!-- Component changes when currentTab changes -->
<component :is="currentTab"></component>
template
<!-- Component changes when currentTab changes -->
<component :is="tabs[currentTab]"></component>

In the example above, the value passed to :is can contain either:

  • the name string of a registered component, OR
  • the actual imported component object

You can also use the is attribute to create regular HTML elements.

When switching between multiple components with <component :is="...">, a component will be unmounted when it is switched away from. We can force the inactive components to stay "alive" with the built-in <KeepAlive> component.

DOM Template Parsing Caveats

If you are writing your Vue templates directly in the DOM, Vue will have to retrieve the template string from the DOM. This leads to some caveats due to browsers' native HTML parsing behavior.

TIP

It should be noted that the limitations discussed below only apply if you are writing your templates directly in the DOM. They do NOT apply if you are using string templates from the following sources:

  • Single-File Components
  • Inlined template strings (e.g. template: '...')
  • <script type="text/x-template">

Case Insensitivity

HTML tags and attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you’re using in-DOM templates, PascalCase component names and camelCased prop names or v-on event names all need to use their kebab-cased (hyphen-delimited) equivalents:

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

Self Closing Tags

We have been using self-closing tags for components in previous code samples:

template
<MyComponent />

This is because Vue's template parser respects /> as an indication to end any tag, regardless of its type.

In DOM templates, however, we must always include explicit closing tags:

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

This is because the HTML spec only allows a few specific elements to omit closing tags, the most common being <input> and <img>. For all other elements, if you omit the closing tag, the native HTML parser will think you never terminated the opening tag. For example, the following snippet:

template
<my-component /> <!-- we intend to close the tag here... -->
<span>hello</span>

will be parsed as:

template
<my-component>
  <span>hello</span>
</my-component> <!-- but the browser will close it here. -->

Element Placement Restrictions

Some HTML elements, such as <ul>, <ol>, <table> and <select> have restrictions on what elements can appear inside them, and some elements such as <li>, <tr>, and <option> can only appear inside certain other elements.

This will lead to issues when using components with elements that have such restrictions. For example:

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

The custom component <blog-post-row> will be hoisted out as invalid content, causing errors in the eventual rendered output. We can use the special is attribute as a workaround:

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

TIP

When used on native HTML elements, the value of is must be prefixed with vue: in order to be interpreted as a Vue component. This is required to avoid confusion with native customized built-in elements.

That's all you need to know about DOM template parsing caveats for now - and actually, the end of Vue's Essentials. Congratulations! There's still more to learn, but first, we recommend taking a break to play with Vue yourself - build something fun, or check out some of the Examples if you haven't already.

Once you feel comfortable with the knowledge you've just digested, move on with the guide to learn more about components in depth.

Grundlagen zu Komponenten has loaded