Vue.js 3.2: Code sparen in Single File Components
Seite 2: SFCs im Wandel der Zeit
Single File Components mit der Dateiendung *.vue vereinen in sich den Template-, den Script- und den Style-Teil für eine Komponente. Eine simple Notiz-Komponente kann so aussehen:
// MyNote.vue
<template>
<form @submit.prevent="saveNote">
<label> Notiz für {{ user }} <MyTextarea v-model="note"></MyTextarea> </label>
<button type="submit">Notiz speichern</button>
</form>
</template>
<script>
import MyTextarea from './MyTextarea.vue'
export default {
components: { MyTextarea },
props: ['user'],
emits: ['saved'],
data() {
return {
note: '',
}
},
methods: {
saveNote() {
// API-Aufruf zum Abspeichern der Notiz
this.$emit('saved')
}
}
</script>
Die Komponente MyNote.vue
besteht aus einem Formular, das eine benutzerdefinierte Komponente MyTextarea.vue
für den Textinhalt importiert. Das Prop user
stellt im Formular den Usernamen dar. Der Textinhalt wird im data
-Teil unter note
gespeichert. Schließlich gibt es noch eine Methode saveNote
, die beim Abschicken des Formulars zum Einsatz kommt. Sie emittiert das Event saved
.
Diese SFC-Syntax gilt seit Version 1 des Frameworks – lediglich die Option emits
kam in Version 3 hinzu – und lässt sich weiterhin problemlos verwenden. Mit der Composition API aus Version 3 kann der <script>
-Teil nun auch wie folgt aussehen:
// MyNote.vue
<script>
import { ref } from 'vue'
import MyTextarea from './MyTextarea.vue'
export default {
components: { MyTextarea },
props: ['user'],
emits: ['saved'],
setup(props, { emit }) {
const note = ref('')
function saveNote() {
// API-Aufruf zum Abspeichern der Notiz
emit('saved')
}
return { note, saveNote }
}
}
</script>
Damit ist auf den ersten Blick nicht viel gewonnen, denn data()
und methods
werden nun vermischt, was eigentlich einen Verstoß gegen die Separation of Concerns darstellt. Jedoch lassen sich nun die Inhalte der setup
-Funktionen in eigene Funktionen – die namensgebenden Composables der Composition API – auslagern. Bei diesen Funktionen hat sich die Community auf das Präfix use
vor dem eigentlichen Funktionsnamen geeinigt. In der Notizen-Komponente wäre also das Refactoring des setup
-Inhalts in eine useNote()
-Funktion denkbar. Der Code wird nun nach der Domäne getrennt und nicht nach data()
und methods
. Ein Composable hat also jetzt nur eine bestimmte Aufgabe, in diesem Fall das Speichern einer Notiz.
In Vue.js 3.2 sieht die Komponente mit <script setup>
wie folgt aus:
// MyNote.vue
<script setup>
import { ref } from 'vue'
import MyTextarea from './MyTextarea.vue'
const props = defineProps(['user'])
const emit = defineEmits(['saved'])
const note = ref('')
function saveNote() {
// API-Aufruf zum Abspeichern der Notiz emit('saved')
}
</script>
Unterschiede zu <script>
Auf den ersten Blick fallen einige Unterschiede zwischen <script>
und dem neueren <script setup>
auf: Erstens existiert export default { ... }
nicht mehr, stattdessen lebt alles im Top-Level-Kontext. Zweitens sind die Props über eine nicht importierte defineProps
-Funktion definiert. defineProps
ist in diesem Fall ein spezielles Compiler-Makro des Vue.js-SFC-Compilers. Beim Umwandeln der SFC wird die defineProps
-Funktion zu normalem JavaScript umgewandelt, das die Props für die Komponente definiert. Da dies zur Compile-Zeit geschieht, ist es nicht notwendig, diese Funktion zu importieren, da es sie zur Laufzeit gar nicht mehr gibt. Genauso verhält es sich bei der Funktion defineEmits
, die die emit
-Funktionen zurückgibt.
Es ist nicht nötig, die importierte Komponente MyTextarea
in components
aufzulisten. Das Importieren genügt, damit sie im <template>
-Bereich verfügbar ist. Das Ref note
und die Funktion saveNote
sind direkt im Top-Level-Kontext verfügbar und es ist nicht erforderlich, sie zurückzugeben.
Im Allgemeinen sind alle Variablen, Funktionen und Imports im Top-Level direkt im <template>
-Bereich als Bindings verwendbar. Das bedeutet jedoch, dass Komponenten mit <script setup>
standardmäßig geschlossen sind. Von außen ist es nicht möglich, via Template Refs auf ihre Bindings zuzugreifen.
<template>
<MyNote ref="note_ref" user="Benutzer"></MyNote>
<button @click="saveNoteViaRef">Speichern von außen</button>
</template>
<script>
import MyNote.vue from './MyNote.vue'
export default {
components: { MyNote },
methods: {
saveNoteViaRef() {
// Nicht möglich, da nicht von MyNote exposed
this.refs.note_ref.save()
}
}
}
</script>
Um dennoch explizit definieren zu können, welche Bindings nach außen sichtbar sein sollen, stellen die Vue.js-Entwickler das zusätzliche Compiler-Makro defineExpose
bereit. Damit das obige Beispiel funktioniert, muss die MyNote.vue-Komponente so aussehen:
// MyNote.vue
<script setup>
// ...
function saveNote() {
// API-Aufruf zum Abspeichern der Notiz emit('saved')}
// Hiermit wird saveNote von außen sichtbar
defineExpose({ saveNote })
</script>
<script> und <script setup> können koexistieren
<script setup>
lässt sich in einer SFC verwenden, die bereits ein <script>
nutzt. Manchmal ist das sogar notwendig, wie etwa beim Verwenden von Plug-ins, die einen bestimmten Schlüssel im Option-Objekt der Komponente erwarten. Auch ist <script>
nötig, um einen einmaligen Seiteneffekt zu erzeugen:
<script>
runSideEffectOnce()
export default {
name: 'CustomComponentName',
inheritAttrs: false,
}
</script>
<script setup>
import { ref } from 'vue'
const name = ref('')
</script>