Einführung in Vue.js: Komponenten, Props und Slots

Avatar of Sarah Drasner
Sarah Drasner am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Dies ist der zweite Teil einer fünfteiligen Serie über das JavaScript-Framework Vue.js. In diesem Teil werden wir uns mit Komponenten, Props und Slots befassen. Dies ist keine vollständige Anleitung, sondern vielmehr ein Überblick über die Grundlagen, damit Sie sofort loslegen können, Vue.js kennenlernen und verstehen, was das Framework zu bieten hat.

Artikelserie

  1. Rendering, Direktiven und Ereignisse
  2. Komponenten, Props und Slots (Sie sind hier!)
  3. Vue-cli
  4. Vuex
  5. Animationen

Komponenten und Datenübergabe

Wenn Sie mit React oder Angular2 vertraut sind, ist die Idee von Komponenten und der Übergabe von Zuständen nichts Neues für Sie. Falls nicht, gehen wir einige der Hauptkonzepte durch.

Websites, ob groß oder klein, bestehen normalerweise aus verschiedenen Teilen, und deren Abstraktion in kleinere Teile macht sie leicht zu strukturieren, zu verstehen, wiederzuverwenden und unseren Code lesbarer zu machen. Anstatt uns durch den gesamten Markup einer langen, vielschichtigen Seite zu graben, könnten wir sie aus Komponenten wie dieser zusammensetzen:

<header></header>
<aside>
  <sidebar-item v-for="item in items"></sidebar-item>
</aside>
<main>
  <blogpost v-for="post in posts"></blogpost>
</main>
<footer></footer>

Dies ist ein vereinfachtes Beispiel, aber Sie können sehen, wie nützlich diese Art der Komposition sein kann, wenn Sie mit dem Aufbau der Struktur Ihrer Website beginnen. Wenn Sie als Wartungsmitarbeiter in diesen Code eintauchen müssten, würde es nicht lange dauern, bis Sie verstehen, wie die Anwendung strukturiert ist oder wo Sie nach jedem Teil suchen müssen.

Vue ermöglicht es uns, Komponenten auf verschiedene Arten zu erstellen. Arbeiten wir uns von einfach zu komplex vor, und bedenken Sie, dass das komplexe Beispiel am ehesten dem entspricht, wie eine durchschnittliche Vue-Anwendung aussehen würde.

new Vue({
  el: 'hello',
  template: '<h1>Hello World!</h1>'
});

Das funktioniert, ist aber nicht besonders nützlich, da es nur einmal verwendet werden kann und wir die Informationen noch nicht an verschiedene Komponenten übergeben. Eine Möglichkeit, Daten von einem Elternteil an ein Kind zu übergeben, nennt man Props.

Dies ist das einfachste Beispiel, das ich machen konnte, damit es super klar ist. Denken Sie daran, dass :text im HTML eine Abkürzung für Vue-Binding ist. Wir haben dies letztes Mal im Abschnitt über Direktiven behandelt. Binding kann für alle möglichen Dinge verwendet werden, aber in diesem Fall erspart es uns, den Zustand in einer Schnabelvorlage zu platzieren, wie z. B. {{ message }}.

Im folgenden Code ist Vue.component die Komponente und new Vue wird als Instanz bezeichnet. Sie können mehr als eine Instanz in einer Anwendung haben. Typischerweise haben wir eine Instanz und mehrere Komponenten, da die Instanz die Hauptanwendung ist.

Vue.component('child', {
  props: ['text'],
  template: `<div>{{ text }}<div>`
});

new Vue({
  el: "#app",
  data() {
    return {
      message: 'hello mr. magoo'
    }
  }
});
<div id="app">
  <child :text="message"></child>
</div>

Sehen Sie sich den Pen an.

Jetzt können wir diese Komponente beliebig oft in unserer Anwendung wiederverwenden.

<div id="app">
  <child :text="message"></child>
  <child :text="message"></child>
</div>

Sehen Sie sich den Pen an.

Wir können unseren Props auch Validierungen hinzufügen, was PropTypes in React ähnelt. Das ist schön, weil es selbstdokumentierend ist und einen Fehler zurückgibt, wenn es nicht das ist, was wir erwartet haben, aber nur im Entwicklungsmodus.

Vue.component('child', {
  props: {
    text: {
      type: String,
      required: true
    }
  },
  template: `<div>{{ text }}<div>`
});

Im folgenden Beispiel lade ich Vue im Entwicklungsmodus und übergebe absichtlich einen ungültigen Typ in unsere Prop-Validierung. Sie können den Fehler in der Konsole sehen. (Es ist auch hilfreich, dass Sie die Devtools von Vue nutzen können und wissen, wo Sie sie finden).

Vue.component('child', {
  props: {
    text: {
      type: Boolean,
      required: true
    }
  },
  template: `<div>{{ text }}<div>`
});

Siehe den Pen einfache Props mit Validierung von Sarah Drasner (@sdras) auf CodePen.

Objekte sollten als Factory-Funktion zurückgegeben werden, und Sie können sogar eine benutzerdefinierte Validatorfunktion übergeben, was wirklich schön ist, da Sie Werte anhand von Geschäftslogik, Eingabelogik oder anderer Logik überprüfen können. Es gibt eine schöne Beschreibung, wie Sie jeden Typ verwenden würden, hier im Leitfaden.

Sie müssen die Daten nicht unbedingt in Props an das Kind übergeben, sondern haben auch die Möglichkeit, Zustände oder statische Werte zu verwenden, wie Sie es für richtig halten.

Vue.component('child', {
  props: { 
    count: {
      type: Number,
      required: true
    }
  },
  template: `<div class="num">{{ count }}</div>`
})

new Vue({
  el: '#app',
  data() {
    return {
      count: 0    
    }
  },
  methods: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    }
  }
})
<div id="app">
  <h3>
    <button @click="increment">+</button>
    Adjust the state
    <button @click="decrement">-</button>
  </h3>
  <h2>This is the app state: <span class="num">{{ count }}</span></h2>
  <hr>
  <h4><child count="1"></child></h4> 
  <p>This is a child counter that is using a static integer as props</p>
  <hr>
  <h4><child :count="count"></child></h4>
  <p>This is the same child counter and it is using the state as props</p>
</div>

Siehe den Pen Kindkomponente mit und ohne Props von Sarah Drasner (@sdras) auf CodePen.

Der Unterschied besteht darin, ob Sie eine Eigenschaft übergeben und sie binden oder nicht.

Zustand nicht verwenden
<kind count="1"></kind>

vs

Zustand verwenden
<kind :count="count"></kind>

Bisher haben wir Inhalte in unserer Kindkomponente als String erstellt, und natürlich, wenn Sie Babel verwenden, damit Sie ES6 in allen Browsern verarbeiten können (was ich sehr empfehle), könnten Sie eine Template-Literal verwenden, um potenziell schwer lesbare String-Verkettungen zu vermeiden.

Vue.component('individual-comment', {
  template: 
  `<li> {{ commentpost }} </li>`,
  props: ['commentpost']
});

new Vue({
  el: '#app',
  data: {
    newComment: '',
    comments: [
      'Looks great Julianne!',
      'I love the sea',
      'Where are you at?'
    ]
  },
  methods: {
    addComment: function () {
      this.comments.push(this.newComment)
      this.newComment = ''
    }
  }
});
<ul>
    <li
      is="individual-comment"
      v-for="comment in comments"
      v-bind:commentpost="comment"
    ></li>
  </ul>
  <input
    v-model="newComment"
    v-on:keyup.enter="addComment"
    placeholder="Add a comment"
  >

Siehe den Pen cd81de1463229a9612dca7559dd666e0 von Sarah Drasner (@sdras) auf CodePen.

Das ist schon etwas nützlicher, aber es gibt immer noch eine Grenze, wie viel Inhalt wir wahrscheinlich in diesen String packen wollen, selbst mit Hilfe von Template-Literalen. Irgendwann in diesem Kommentarformular möchten wir Fotos und die Namen der Autoren haben, und Sie können sich wahrscheinlich schon vorstellen, wie überfüllt es mit all diesen Informationen werden würde. Wir werden auch keine nützliche Syntaxhervorhebung in diesem String haben.

Mit all diesen Dingen im Hinterkopf erstellen wir eine Vorlage. Wir umschließen reguläres HTML mit speziellen Skript-Tags und verwenden eine ID, um darauf zu verweisen, um eine Komponente zu erstellen. Sie können sehen, dass dies viel lesbarer ist, wenn wir viel Text und Elemente haben.

<!-- This is the Individual Comment Component -->
<script type="text/x-template" id="comment-template">
<li> 
  <img class="post-img" :src="commentpost.authorImg" /> 
  <small>{{ commentpost.author }}</small>
  <p class="post-comment">"{{ commentpost.text }}"</p>
</li>
</script>
Vue.component('individual-comment', {
  template: '#comment-template',
  props: ['commentpost']
});

Siehe den Pen Photo App Post mit Vue.js von Sarah Drasner (@sdras) auf CodePen.

Slots

Das ist schon viel besser. Aber was passiert, wenn wir zwei Komponenten mit leichten Variationen haben, sei es inhaltlich oder stilistisch? Wir könnten all die verschiedenen Inhalte und Stile über Props in die Komponente übergeben und jedes Mal alles ändern, oder wir könnten die Komponenten selbst forken und verschiedene Versionen davon erstellen. Aber es wäre wirklich schön, wenn wir die Komponenten wiederverwenden und sie mit denselben Daten oder Funktionalitäten füllen könnten. Hier sind Slots wirklich praktisch.

Nehmen wir an, wir haben eine Haupt-App-Instanz, die zweimal dieselbe <app-child> Komponente verwendet. Innerhalb jedes Kindes möchten wir einige gleiche Inhalte und einige unterschiedliche Inhalte haben. Für die Inhalte, die gleich bleiben sollen, verwenden wir ein normales p-Tag, und für die Inhalte, die wir austauschen möchten, setzen wir ein leeres <slot></slot> Tag.

<script type="text/x-template" id="childarea">
  <div class="child">
    <slot></slot>
    <p>It's a veritable slot machine!<br> 
    Ha ha aw</p>
  </div>
</script>

Dann können wir in der App-Instanz Inhalte innerhalb der <app-child> Komponententags übergeben, und diese werden automatisch die Slots füllen.

<div id="app">
  <h2>We can use slots to populate content</h2>
  <app-child>
    <h3>This is slot number one</h3>
  </app-child>
  <app-child>
    <h3>This is slot number two</h3>
    <small>I can put more info in, too!</small>
  </app-child>
</div>

Sehen Sie sich den Pen an.

Sie können auch Standardinhalte in Slots haben. Wenn Sie im Slot selbst statt <slot></slot> schreiben, können Sie ihn mit folgendem füllen:

<slot>Ich bin etwas Standardtext</slot>

Dieser Standardtext wird verwendet, bis Sie den Slot mit anderem Material füllen, was sehr nützlich ist! Hoch die Hände für alle.

Sie können auch benannte Slots haben. Wenn Sie zwei Slots in einer Komponente hätten, könnten Sie sie unterscheiden, indem Sie ein name-Attribut hinzufügen <slot name="headerinfo"></slot> und wir könnten auf diesen speziellen Slot zugreifen, indem wir <h1 slot="headerinfo">Ich werde den headerinfo-Slot füllen!</h1> schreiben. Das ist äußerst nützlich. Wenn Sie mehrere benannte Slots und einen unbenannten haben, wird Vue den benannten Inhalt in die benannten Slots einfügen, und der Rest wird verwendet, um die verbleibenden unbenannten Slots zu füllen.

Hier ist ein Beispiel dafür, was ich meine.

Dies ist eine Beispiel-Kindvorlage.

<div id="post">
  <main>
    <slot name="header"></slot>
    <slot></slot>
  </main>
</div>

Dies ist ein Beispiel für die Elternkomponente.

<app-post>
  <h1 slot="header">This is the main title</h1>
  <p>I will go in the unnamed slot!</p>
</app-post>

Gerenderter Inhalt.

<main>
  <h1>This is the main title</h1>
  <p>I will go in the unnamed slot!</p>
</main>

Persönlich benenne ich, wenn ich mehr als einen Slot gleichzeitig verwende, alle, damit es super klar ist, was wohin geht für andere Wartungsmitarbeiter, aber es ist schön, dass Vue eine so flexible API bietet.

Slot-Beispiel

Alternativ können wir bestimmte Stile für verschiedene Komponenten zuweisen und alle Inhalte im selben belassen, wodurch wir schnell und einfach das Erscheinungsbild von etwas ändern können. Im folgenden Wein-Etiketten-Maker schaltet einer der Knöpfe die Komponente und die Farbe basierend auf der Auswahl des Benutzers um, und der Hintergrund der Flasche und des Etiketts sowie der Text ändern sich, während der Inhalt stabil bleibt.

const app = new Vue({
  ...
  components: {
    'appBlack': {
      template: '#black'
    }
  }
});

HTML der Haupt-Vue-App

  <component :is="selected">
    ...
    <path class="label" d="M12,295.9s56.5,5,137.6,0V409S78.1,423.6,12,409Z" transform="translate(-12 -13.8)" :style="{ fill: labelColor }"/>
    ...
  </component>

<h4>Color</h4>
  <button @click="selected ='appBlack', labelColor = '#000000'">Black Label</button>
  <button @click="selected ='appWhite', labelColor = '#ffffff'">White Label</button>
  <input type="color" v-model="labelColor" defaultValue="#ff0000">

HTML der weißen Komponente

<script type="text/x-template" id="white">
  <div class="white">
     <slot></slot>
  </div>
</script>

(Dies ist eine größere Demo, daher macht es vielleicht mehr Sinn, wenn Sie sie in einem separaten Fenster/Tab ausprobieren)

Sehen Sie sich den Pen an.

Wine Label Switching Out

Jetzt legen wir alle SVG-Bilddaten in die Haupt-App, aber sie sind tatsächlich innerhalb des <slot> in jeder Komponente platziert. Dies ermöglicht es uns, Inhaltsteile auszutauschen oder Dinge anders zu stylen, je nach Verwendung, was ein wirklich nettes Feature ist. Sie können sehen, dass wir dem Benutzer durch Erstellen eines Buttons, der den "selected"-Wert der Komponente ändert, die Entscheidung überlassen haben, welche Komponente er verwenden wird.

Momentan haben wir alles in einem Slot, aber wir könnten auch mehrere Slots verwenden und sie durch Benennung unterscheiden, wenn wir möchten.

<!-- main vue app instance -->
<app-comment>
  <p slot="comment">{{ comment.text }}</p>
</app-comment>

<!-- individual component -->
<script type="text/x-template" id="comment-template">
  <div>
    <slot name="comment"></slot>
  </div>
</script>

Wir können einfach zwischen verschiedenen Komponenten mit denselben referenzierten Slots wechseln, aber was passiert, wenn wir hin und her wechseln können möchten, aber den individuellen Zustand jeder Komponente beibehalten wollen? Derzeit, wenn wir zwischen Schwarz und Weiß wechseln, werden die Vorlagen ausgetauscht und der Inhalt bleibt derselbe. Aber vielleicht haben wir eine Situation, in der wir möchten, dass das schwarze Etikett völlig anders ist als das weiße Etikett. Es gibt eine spezielle Komponente, in die Sie es einpacken können, namens <keep-alive></keep-alive>, die den Zustand beim Wechsel beibehält.

Schauen Sie sich diese Abwandlung des obigen Beispiels an – erstellen Sie ein schwarzes Etikett und dann ein anderes weißes Etikett und wechseln Sie dazwischen. Sie werden sehen, dass der Zustand jedes Etiketts erhalten bleibt und sie sich voneinander unterscheiden.

<keep-alive>
  <component :is="selected">
    ...
  </component>
</keep-alive>
Wine Label Keep-Alive Demo

Siehe den Pen Vue Wein-Etiketten-Macher – mit keep-alive von Sarah Drasner (@sdras) auf CodePen.

Ich liebe diese Funktion der API.

Das ist alles schön und gut, aber aus Gründen der Einfachheit haben wir alles in einer oder zwei Dateien belassen. Es wäre viel besser organisiert, wenn wir beim Erstellen unserer Website die Komponenten in verschiedene Dateien aufteilen und sie bei Bedarf importieren könnten, und das ist tatsächlich so, wie die reale Entwicklung in Vue typischerweise aussieht. Lassen Sie uns das als Nächstes durchgehen. Schalten Sie ein für den nächsten Teil, wenn wir über Vue-cli, Build-Prozesse und Vuex für State Management sprechen!

Artikelserie

  1. Rendering, Direktiven und Ereignisse
  2. Komponenten, Props und Slots (Sie sind hier!)
  3. Vue-cli
  4. Vuex
  5. Animationen