Beginnen wir mit einer kurzen Einführung in das Konzept der Slots in Vue.js. Slots sind nützlich, wenn Sie Inhalte an einer bestimmten Stelle einer Komponente einfügen möchten. Diese bestimmten Stellen, die Sie definieren können, werden als Slots bezeichnet.
Zum Beispiel möchten Sie eine Wrapper-Komponente erstellen, die auf eine bestimmte Weise gestylt ist, aber Sie möchten beliebigen Inhalt übergeben können, der innerhalb dieses Wrappers gerendert werden soll (es kann sich um eine Zeichenkette, einen berechneten Wert oder sogar eine andere Komponente handeln).
Es gibt drei Arten von Slots
- Standard- / unbenannte Slots: werden verwendet, wenn eine Komponente einen einzelnen Slot hat. Wir erstellen sie, indem wir
<slot>in der Vorlage hinzufügen, wo wir unseren Inhalt einfügen möchten. Dieses<slot>-Tag wird durch jeden Inhalt ersetzt, der an die Vorlage der Komponente übergeben wird. - Benannte Slots: werden verwendet, wenn eine Komponente mehrere Slots hat und wir unterschiedliche Inhalte an verschiedenen Stellen (Slots) einfügen möchten. Wir erstellen diese, indem wir
<slot>mit einemname-Attribut hinzufügen (z. B.<slot name="header"></slot>). Wenn wir dann unsere Komponente rendern, stellen wir für jeden benannten Slot einen Slot-Inhalt bereit, indem wir einslot-Attribut mit dem Slot-Namen hinzufügen.
<base-layout>
<template slot="header">
<h1>My awsome header</h1>
</template>
<template slot="footer">
<p>My awsome footer</p>
</template>
</base-layout>
Dadurch werden die <slot>-Tags in der Komponente durch den an die Komponente übergebenen Inhalt ersetzt.
- Scoped Slot: wird verwendet, wenn eine Vorlage innerhalb eines Slots auf Daten aus der Kindkomponente zugreifen soll, die den Slot-Inhalt rendert. Dies ist besonders nützlich, wenn Sie Freiheit bei der Erstellung benutzerdefinierter Vorlagen benötigen, die die Daten-Eigenschaften der Kindkomponente verwenden.

Praxisbeispiel: Erstellung einer Google Map Loader-Komponente
Stellen Sie sich eine Komponente vor, die eine externe API konfiguriert und vorbereitet, um in einer anderen Komponente verwendet zu werden, aber nicht eng mit einer bestimmten Vorlage gekoppelt ist. Eine solche Komponente könnte dann an mehreren Stellen wiederverwendet werden, die unterschiedliche Vorlagen rendern, aber dasselbe Basisobjekt mit einer bestimmten API verwenden.
Ich habe eine Komponente (GoogleMapLoader.vue) erstellt, die
- die Google Maps API initialisiert
google- undmap-Objekte erstellt- diese Objekte an die Elternkomponente weitergibt, in der
GoogleMapLoaderverwendet wird
Unten finden Sie ein Beispiel dafür, wie dies erreicht werden kann. Wir werden den Code Stück für Stück analysieren und im nächsten Abschnitt sehen, was tatsächlich passiert.
Lassen Sie uns zunächst die Vorlage unserer GoogleMapLoader.vue festlegen
<template>
<div>
<div class="google-map" data-google-map></div>
<template v-if="Boolean(this.google) && Boolean(this.map)">
<slot :google="google" :map="map" />
</template>
</div>
</template>
Als Nächstes muss unser Skript einige Props von der Elternkomponente empfangen, die es uns ermöglichen, die Google Maps API und das Map-Objekt festzulegen
import GoogleMapsApiLoader from "google-maps-api-loader";
export default {
props: {
mapConfig: Object,
apiKey: String
},
data() {
return {
google: null,
map: null
};
},
async mounted() {
const googleMapApi = await GoogleMapsApiLoader({
apiKey: this.apiKey
});
this.google = googleMapApi;
this.initializeMap();
},
methods: {
initializeMap() {
const mapContainer = this.$el.querySelector("[data-google-map]");
this.map = new this.google.maps.Map(mapContainer, this.mapConfig);
}
}
};
Dies ist nur ein Teil eines funktionierenden Beispiels. Sie können sich dieses Beispiel genauer ansehen.
Okay, jetzt, da unser Anwendungsfall eingerichtet ist, lassen Sie uns den Code aufschlüsseln, um zu untersuchen, was er tut.
1. Erstellen Sie eine Komponente, die unsere Karte initialisiert
In der Vorlage erstellen wir einen Container für die Karte, der zum Einbinden des Map-Objekts aus der Google Maps API verwendet wird.
// GoogleMapLoader.vue
<template>
<div>
<div class="google-map" data-google-map></div>
</div>
</template>
Als Nächstes muss unser Skript Props von der Elternkomponente empfangen, die es uns ermöglicht, die Google Map einzurichten. Diese Props bestehen aus
mapConfig: Google Maps KonfigurationsobjektapiKey: Unser persönlicher API-Schlüssel, der von Google Maps benötigt wird
// GoogleMapLoader.vue
import GoogleMapsApiLoader from "google-maps-api-loader";
export default {
props: {
mapConfig: Object,
apiKey: String
},
Dann setzen wir die Anfangswerte von google und map auf null
data() {
return {
google: null,
map: null
};
},
Im `mounted`-Hook erstellen wir eine Instanz von googleMapApi und das map-Objekt daraus. Wir müssen auch die Werte von google und map auf die erstellten Instanzen setzen
async mounted() {
const googleMapApi = await GoogleMapsApiLoader({
apiKey: this.apiKey
});
this.google = googleMapApi;
this.initializeMap();
},
methods: {
initializeMap() {
const mapContainer = this.$el.querySelector("[data-google-map]");
this.map = new this.google.maps.Map(mapContainer, this.mapConfig);
}
}
};
Bisher alles gut. Wenn das alles erledigt ist, könnten wir weitere Objekte zur Karte hinzufügen (Marker, Polylinien usw.) und sie als gewöhnliche Kartenkomponente verwenden.
Aber wir wollen unsere GoogleMapLoader-Komponente nur als Loader verwenden, der die Karte vorbereitet – wir wollen nichts darauf rendern.
Um dies zu erreichen, müssen wir der Elternkomponente, die GoogleMapLoader verwenden wird, den Zugriff auf this.google und this.map ermöglichen, die innerhalb der GoogleMapLoader-Komponente gesetzt werden. Hier glänzen Scoped Slots wirklich. Scoped Slots ermöglichen es uns, die Eigenschaften einer Kindkomponente an die Elternkomponente weiterzugeben. Das mag wie eine Inception klingen, aber bleiben Sie noch eine Minute bei mir, während wir das weiter aufschlüsseln.
2. Erstellen Sie eine Komponente, die unsere Initialisierungskomponente verwendet
In der Vorlage rendern wir die GoogleMapLoader-Komponente und übergeben die Props, die zur Initialisierung der Karte erforderlich sind.
// TravelMap.vue
<template>
<GoogleMapLoader
:mapConfig="mapConfig"
apiKey="yourApiKey"
/>
</template>
Unser Skript-Tag sollte so aussehen
import GoogleMapLoader from "./GoogleMapLoader";
import { mapSettings } from "@/constants/mapSettings";
export default {
components: {
GoogleMapLoader,
},
computed: {
mapConfig() {
return {
...mapSettings,
center: { lat: 0, lng: 0 }
};
},
}
};
Immer noch keine Scoped Slots, also fügen wir einen hinzu.
3. Richten Sie die Eigenschaften `google` und `map` für die Elternkomponente ein, indem Sie einen Scoped Slot hinzufügen
Schließlich können wir einen Scoped Slot hinzufügen, der die Aufgabe erfüllt und uns den Zugriff auf die Props der Kindkomponente in der Elternkomponente ermöglicht. Wir tun dies, indem wir das <slot>-Tag in der Kindkomponente hinzufügen und die Props übergeben, die wir weitergeben möchten (mit der v-bind-Direktive oder der Kurzform :propName). Es unterscheidet sich nicht vom Übergeben von Props an die Kindkomponente, aber die Ausführung im <slot>-Tag kehrt die Richtung des Datenflusses um.
// GoogleMapLoader.vue
<template>
<div>
<div class="google-map" data-google-map></div>
<template v-if="Boolean(this.google) && Boolean(this.map)">
<slot
:google="google"
:map="map"
/>
</template>
</div>
</template>
Nachdem wir nun den Slot in der Kindkomponente haben, müssen wir die weitergegebenen Props in der Elternkomponente empfangen und verbrauchen.
4. Empfangen Sie weitergegebene Props in der Elternkomponente mit dem `slot-scope`-Attribut
Um die Props in der Elternkomponente zu empfangen, deklarieren wir ein Template-Element und verwenden das slot-scope-Attribut. Dieses Attribut hat Zugriff auf das Objekt, das alle von der Kindkomponente weitergegebenen Props enthält. Wir können das gesamte Objekt abrufen oder wir können dieses Objekt de-strukturieren und nur das nehmen, was wir brauchen.
Lassen Sie uns dieses Ding de-strukturieren, um das zu bekommen, was wir brauchen.
// TravelMap.vue
<template>
<GoogleMapLoader
:mapConfig="mapConfig"
apiKey="yourApiKey"
>
<template slot-scope="{ google, map }">
{{ map }}
{{ google }}
</template>
</GoogleMapLoader>
</template>
Obwohl die Props google und map im Geltungsbereich von TravelMap nicht existieren, hat die Komponente Zugriff darauf und wir können sie in der Vorlage verwenden.
Ja, okay, aber warum würde ich so etwas tun? Wozu ist das alles gut?
Gut gefragt! Scoped Slots ermöglichen es uns, der Vorlage einen Slot anstelle eines gerenderten Elements zu übergeben. Es wird *scoped* Slot genannt, weil es Zugriff auf bestimmte Daten der Kindkomponente hat, obwohl die Vorlage im Geltungsbereich der Elternkomponente gerendert wird. Das gibt uns die Freiheit, die Vorlage mit benutzerdefinierten Inhalten aus der Elternkomponente zu füllen.
5. Erstellen Sie Factory-Komponenten für Marker und Polylinien
Nun, da unsere Karte bereit ist, erstellen wir zwei Factory-Komponenten, die zum Hinzufügen von Elementen zur TravelMap verwendet werden.
// GoogleMapMarker.vue
import { POINT_MARKER_ICON_CONFIG } from "@/constants/mapSettings";
export default {
props: {
google: {
type: Object,
required: true
},
map: {
type: Object,
required: true
},
marker: {
type: Object,
required: true
}
},
mounted() {
new this.google.maps.Marker({
position: this.marker.position,
marker: this.marker,
map: this.map,
icon: POINT_MARKER_ICON_CONFIG
});
},
};
// GoogleMapLine.vue
import { LINE_PATH_CONFIG } from "@/constants/mapSettings";
export default {
props: {
google: {
type: Object,
required: true
},
map: {
type: Object,
required: true
},
path: {
type: Array,
required: true
}
},
mounted() {
new this.google.maps.Polyline({
path: this.path,
map: this.map,
...LINE_PATH_CONFIG
});
},
};
Beide erhalten google, das wir verwenden, um das benötigte Objekt (Marker oder Polyline) zu extrahieren, sowie map, das uns eine Referenz auf die Karte gibt, auf der wir unser Element platzieren möchten.
Jede Komponente erwartet außerdem eine zusätzliche Prop, um ein entsprechendes Element zu erstellen. In diesem Fall haben wir jeweils marker und path.
Im `mounted`-Hook erstellen wir ein Element (Marker/Polyline) und hängen es an unsere Karte an, indem wir die map-Eigenschaft an den Objektkonstruktor übergeben.
Es gibt noch einen Schritt zu tun...
6. Elemente zur Karte hinzufügen
Verwenden wir unsere Factory-Komponenten, um Elemente zu unserer Karte hinzuzufügen. Wir müssen die Factory-Komponente rendern und die google- und map-Objekte übergeben, damit die Daten an die richtigen Stellen fließen.
Wir müssen auch die Daten bereitstellen, die für das Element selbst erforderlich sind. In unserem Fall ist das das marker-Objekt mit der Position des Markers und das path-Objekt mit den Polyline-Koordinaten.
Hier integrieren wir die Datenpunkte direkt in die Vorlage
// TravelMap.vue
<template>
<GoogleMapLoader
:mapConfig="mapConfig"
apiKey="yourApiKey"
>
<template slot-scope="{ google, map }">
<GoogleMapMarker
v-for="marker in markers"
:key="marker.id"
:marker="marker"
:google="google"
:map="map"
/>
<GoogleMapLine
v-for="line in lines"
:key="line.id"
:path.sync="line.path"
:google="google"
:map="map"
/>
</template>
</GoogleMapLoader>
</template>
Wir müssen die erforderlichen Factory-Komponenten in unserem Skript importieren und die Daten festlegen, die an die Marker und Linien übergeben werden
import { mapSettings } from "@/constants/mapSettings";
export default {
components: {
GoogleMapLoader,
GoogleMapMarker,
GoogleMapLine
},
data() {
return {
markers: [
{ id: "a", position: { lat: 3, lng: 101 } },
{ id: "b", position: { lat: 5, lng: 99 } },
{ id: "c", position: { lat: 6, lng: 97 } }
],
lines: [
{ id: "1", path: [{ lat: 3, lng: 101 }, { lat: 5, lng: 99 }] },
{ id: "2", path: [{ lat: 5, lng: 99 }, { lat: 6, lng: 97 }] }
]
};
},
computed: {
mapConfig() {
return {
...mapSettings,
center: this.mapCenter
};
},
mapCenter() {
return this.markers[1].position;
}
}
};
Und wir sind fertig!
Mit all diesen abgeschlossenen Teilen können wir nun die GoogleMapLoader-Komponente als Basis für alle unsere Karten wiederverwenden, indem wir für jede von ihnen unterschiedliche Vorlagen übergeben. Stellen Sie sich vor, Sie müssen eine weitere Karte mit anderen Markern oder nur Markern ohne Polylinien erstellen. Durch die Verwendung eines Musters von Scoped Slots wird dies sehr einfach, da wir jetzt nur noch unterschiedliche Inhalte an die GoogleMapLoader-Komponente übergeben müssen.
Dieses Muster ist nicht streng an Google Maps gebunden; es kann mit jeder Bibliothek verwendet werden, um die Basiskomponente einzurichten und die API der Bibliothek weiterzugeben, die dann in der Komponente verwendet werden kann, die die Basiskomponente aufgerufen hat.
Es mag verlockend sein, eine komplexere oder robustere Lösung zu erstellen, aber dies verschafft uns die benötigte Abstraktion und macht es zu einem unabhängigen Teil unserer Codebasis. Wenn wir diesen Punkt erreichen, könnte es sich lohnen, eine Extraktion in ein Add-on in Betracht zu ziehen.