Komponenten aufteilen

Die Komponentisierung ist die zentrale Designphilosophie von Vue.

In diesem Kapitel lernst du, wie du eine Seite in wiederverwendbare kleine Komponenten aufteilst und über props und emit die Kommunikation zwischen Komponenten herstellst.


Warum Komponenten aufteilen?

Wenn der Funktionsumfang wächst, wird eine Datei immer länger, und Hunderte von Codezeilen, die in eine einzige .vue-Datei gequetscht sind, werden schwer zu pflegen.

Der Zweck des Aufteilens von Komponenten:Jede Komponente erledigt nur eine Sache, und gemeinsam erfüllen sie komplexe Funktionalität.。

Welche Granularität ist angemessen?

Eine gute Faustregel ist:Wenn ein Codeblock eigene Daten und eigenes Verhalten hat und an anderer Stelle wiederverwendet werden kann,sollte er zu einer eigenständigen Komponente werden.

SeitenbereichOb in eine Komponente aufgeteilt werden sollGrund
NavigationsleisteJaAuf jeder Seite erforderlich; eigenständige Funktionalität
ArtikelkarteJaJeder Artikel in der Liste hat die gleiche Struktur
Kategoriefilter-SchaltflächengruppeJaHat einen eigenen Zustand und Interaktionen
Einfacher Wrapper-ContainerneinNur für Layout, kein eigenständiges Verhalten

Eltern-zu-Kind: defineProps

Die Elternkomponente übergibt Daten an die Kindkomponente,propsimplementiert durch defineProps.

Du kannst es dir als "Funktionsparameter" vorstellen — wenn die Elternkomponente die Kindkomponente aufruft, übergibt sie Daten als Parameter.

Beispiel

<!-- Dateipfad: src/components/BlogCard.vue -->
<script setup>
// Deklariere die Props, die diese Komponente empfängt
// defineProps ist ein Compiler-Makro, kein Import erforderlich
const props = defineProps({
title: String, // Artikelüberschrift, String-Typ
summary: String, // Artikelzusammenfassung
date: String, // Veröffentlichungsdatum
category: String // Kategorie-Tag
})

// Es kann auch in Array-Form geschrieben werden (ohne Typprüfung):
// const props = defineProps(['title', 'summary', 'date', 'category'])
</script>

<template>
  <div class="card">
    <span class="tag">{{ category }}</span>
    <h3>{{ title }}</h3>
    <p>{{ summary }}</p>
    <span class="date">{{ date }}</span>
  </div>
</template>

<style scoped>
.card {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.tag {
  display: inline-block;
  padding: 2px 10px;
  background: #E8EEF5;
  color: #427DB8;
  border-radius: 12px;
  font-size: 12px;
}
.card h3 { margin: 10px 0 8px; font-size: 18px; }
.card p { color: #666; font-size: 14px; line-height: 1.6; }
.date { font-size: 12px; color: #999; }
</style>

Wenn die Elternkomponente BlogCard verwendet, übergibt sie Daten durch Binden von Attributen mit einem Doppelpunkt:

Beispiel

<!-- Dateipfad: src/views/HomeView.vue -->
<script setup>
import { ref } from 'vue'
import BlogCard from '../components/BlogCard.vue'

const articles = ref([
{ id: 1, title: 'Vue3 Einführung', summary: 'fromzero lernt Vue3', date: '2024-05-10', category: 'Vue' },
{ id: 2, title: 'Promise im Detail', summary: 'Async verstehen', date: '2024-05-08', category: 'JavaScript' },
])
</script>

<template>
  <div class="article-grid">
    <!-- Die Elternkomponente übergibt Daten an die Kindkomponente über :prop-name="value" -->
    <BlogCard
     v-for="article in articles"
     :key="article.id"
     :title="article.title"
     :summary="article.summary"
     :date="article.date"
     :category="article.category"
   />
  </div>
</template>

props sindein unidirektionaler Datenfluss: Daten können nur von der Eltern- zur Kindkomponente fließen; die Kindkomponente kann den Wert von props nicht ändern. Wenn die Kindkomponente ihn ändern muss, sollte sie die Elternkomponente über emit benachrichtigen, um die Änderung vorzunehmen.


Von Kind zu Eltern: defineEmits

Wenn eine Kindkomponente die Elternkomponente darüber benachrichtigen muss, dass etwas passiert ist (z. B. "Ich wurde angeklickt"), tut sie dies, indem sieemitein Event auslöst.

Du kannst es dir als eine "Callback-Funktion" vorstellen — die Kindkomponente löst ein Event aus, und die Elternkomponente hört zu und reagiert.

Beispiel

<!-- Dateipfad: src/components/CategoryFilter.vue -->
<script setup>
const props = defineProps({
categories: Array, // Liste aller Kategorien
activeCategory: String // Aktuell ausgewählte Kategorie
})

// Die zu emittierenden Events deklarieren
const emit = defineEmits(['update-category'])

function selectCategory(cat) {
// Das Event auslösen und die ausgewählte Kategorie an die Elternkomponente übergeben
  emit('update-category', cat)
}
</script>

<template>
  <div class="filter-bar">
    <button
     v-for="cat in categories"
     :key="cat"
     :class="{ active: activeCategory === cat }"
     @click="selectCategory(cat)"
   >
      {{ cat }}
    </button>
  </div>
</template>

Die Elternkomponente verwendet@eventName="handlerFunction"um auf Events der Kindkomponente zu hören:

Beispiel

<!-- Dateipfad: src/views/HomeView.vue -->
<script setup>
import { ref, computed } from 'vue'
import CategoryFilter from '../components/CategoryFilter.vue'

const activeCategory = ref('All')

const categories = ['All', 'Vue', 'JavaScript', 'CSS']

// Auf das von der Kindkomponente ausgelöste update-category-Ereignis reagieren
function handleCategoryChange(cat) {
  activeCategory.value = cat
}
</script>

<template>
  <!-- @update-category="handleCategoryChange" -->
  <CategoryFilter
   :="categories"
   active-category="activeCategory"
   @update-category="handleCategoryChange"
 />
</template>

Vollständiger Datenfluss

Die Elternkomponente übergibt Daten überprops nach unten;die Kindkomponente, überemit,sendet Ereignisse nach oben.

Daten sind immer "im Besitz" der Elternkomponente; die Kindkomponente ist nur für das Rendern und Benachrichtigen verantwortlich.


Praktisch: Drei Komponenten herauslösen

Das Blog-Projekt benötigt drei Komponenten mit der folgenden Verzeichnisstruktur:

src/
├── components/
│   ├── NavBar.vue          # 顶部导航栏
│   ├── BlogCard.vue        # 单篇文章卡片
│   └── CategoryFilter.vue  # 分类筛选按钮组
├── views/
│   └── HomeView.vue        # 首页(父组件)
└── App.vue

NavBar.vue — Navigationsleiste

Beispiel

<!-- Dateipfad: src/components/NavBar.vue -->
<script setup>
// Die Navigationsleiste muss vorerst nur angezeigt werden; Suche und Dunkelmodus-Umschalter werden später hinzugefügt
</script>

<template>
  <header class="navbar">
    <a href="/" class="logo">BEISPIEL Blog</a>
    <nav>
      <a href="/">Startseite</a>
      <a href="#">Über uns</a>
    </nav>
  </header>
</template>

<style scoped>
.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 40px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.logo {
  font-size: 22px;
  font-weight: bold;
  color: #427DB8;
  text-decoration: none;
}

nav a {
  margin-left: 24px;
  text-decoration: none;
  color: #333;
  font-size: 15px;
}

nav a:hover {
  color: #427DB8;
}
</style>

HomeView.vue — Integration aller Komponenten

Beispiel

<!-- Dateipfad: src/views/HomeView.vue -->
<script setup>
import { ref, computed } from 'vue'
import BlogCard from '../components/BlogCard.vue'
import CategoryFilter from '../components/CategoryFilter.vue'

// Artikeldaten
const articles = ref([
{ id: 1, title: 'Vue3 Einstieg: Der komplette Leitfaden', summary: 'Vue3 Composition API von Null an lernen', date: '2024-05-10', category: 'Vue' },
{ id: 2, title: 'JS asynchrone Programmierung im Detail', summary: Promise und async/await verstehen, date: '2024-05-08', category: 'JavaScript' },
{ id: 3, title: Praxisorientiertes CSS-Grid-Layout, summary: 'Responsives Layout mit Grid implementieren', date: '2024-05-05', category: 'CSS' },
{ id: 4, title: 'Vue3-Reaktivitätsprinzip', summary: 'ref und reactive im Detail verstehen', date: '2024-05-03', category: 'Vue' },
{ id: 5, title: 'Der vollständige Leitfaden zu Flexbox', summary: 'Flex-Layout in einem Artikel lernen', date: '2024-05-01', category: 'CSS' },
])

// Alle Kategorien extrahieren (Duplikate entfernen)
const categories = computed(() => {
  const cats = articles.value.map(a => a.category)
return ['Alle', ...new Set(cats)]
})

// Die aktuell ausgewählte Kategorie
const activeCategory = ref('All')

// Artikel nach Kategorie filtern
const filteredArticles = computed(() => {
if (activeCategory.value === 'Alle') return articles.value
  return articles.value.filter(a => a.category === activeCategory.value)
})

// Das von der Kindkomponente ausgelöste Kategoriewechsel-Ereignis behandeln
function handleCategoryChange(cat) {
  activeCategory.value = cat
}
</script>

<template>
  <div class="home">
    <h2 class="section-title">Neueste Artikel</h2>

    <!-- Kategorie-Filter-Komponente -->
    <CategoryFilter
     :="categories"
     active-category="activeCategory"
     @update-category="handleCategoryChange"
   />

    <!-- Leerer Zustand -->
    <p v-if="filteredArticles.length === 0" class="empty-tip">
Noch keine Artikel in dieser Kategorie.
    </p>

    <!-- Liste der Artikelkarten -->
    <div v-else class="article-grid">
      <BlogCard
       v-for="article in filteredArticles"
       :key="article.id"
       :title="article.title"
       :summary="article.summary"
       :date="article.date"
       :category="article.category"
     />
    </div>
  </div>
</template>

<style scoped>
.home { max-width: 960px; margin: 0 auto; padding: 20px; }
.section-title { font-size: 24px; margin-bottom: 20px; }
.article-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
  margin-top: 20px;
}
.empty-tip { text-align: center; color: #999; padding: 60px 0; }
</style>

Kapitelzusammenfassung

In diesem Kapitel hast du die drei Kernkonzepte der Komponentisierung gemeistert: warum Komponenten aufgeteilt werden sollten, die Verwendung von Props (defineProps) für die Kommunikation von der Eltern- zur Kindkomponente und die Verwendung von emit (defineEmits) für die Kommunikation von der Kind- zur Elternkomponente.

Die drei Komponenten des Blogprojekts — NavBar, BlogCard und CategoryFilter — sind jetzt vorhanden, und die Codestruktur ist viel klarer als das Chaos in Kapitel 1.

Andere Erweiterungen