Zum Inhalt
wannacode

Formulare in einer Komponente

Kurz gesagt: v-model in Vue verbindet ein Formularfeld in beide Richtungen mit dem Zustand, und so ist der Zustand die einzige Quelle für das Prüfen und Senden des Formulars.

Problem

Eingetippt, aber leer

Der Kunde füllt die Bestellung aus und drückt auf „Bestellen“. Der Server antwortet, der Name sei erforderlich. Und das Feld „Name“ zeigt den Namen.

Vue

Tipp einen Namen, eine E-Mail-Adresse und eine Adresse ein, akzeptiere die Bedingungen und drück auf „Bestellen“. Das Feld behält „Maria Ivanova“, aber die Anfrage trägt name: ''. Die E-Mail und die Adresse haben v-model, der Name nicht. Der Code schickt den Zustand, und der Zustand weiß nicht, was im Feld steht.

Die Idee

Eine Wahrheit: der Zustand

In der Lektion über das DOM hat das Formular den Text selbst gehalten, und der Code hat ihn beim Absenden gelesen. In einer Komponente gibt es einen zweiten Ort: den Zustand. Sind die beiden nicht verbunden, sagt jeder etwas anderes, wie im Beispiel.

Die Lösung: Das Feld hat keine eigene Wahrheit. Es zeigt den Wert aus dem Zustand und sagt dem Zustand bei jeder Taste den neuen Text. Ein solches Feld heißt kontrolliert (englisch: controlled input).

<script setup>
const name = ref('');
</script>
<template>
<input id="name" v-model="name" />
</template>

v-model ist die Kurzform für beide Richtungen: :value="name" (vom Zustand zum Feld) und @input="name = $event.target.value" (der Weg zurück). Das Ereignis input kommt bei jeder Taste.

v-model kennt die Art des Feldes. An einem Select und an einer Checkbox schreibst du also dasselbe:

<select v-model="payment">…</select>
<input type="checkbox" v-model="agree" />

An einer Checkbox ist agree true oder false, und an einem Select ist es der value der gewählten Option.

Jetzt ist der Zustand die einzige Quelle: Was in ihm steht, steht im Feld, und umgekehrt. Der Button lässt sich über den Zustand deaktivieren (!agree), und die Anfrage schickt genau das, was der Kunde sieht.

Sieh es dir an

Sieh dir den Kreis an

Ein Buchstabe geht den ganzen Weg: Feld, Ereignis, Zustand, neues Zeichnen und wieder das Feld. Danach dasselbe Feld ohne Bindung.

<script setup>
const name = ref('');
</script>
<template>
<form @submit.prevent="submit">
<label for="name">Name</label>
<input id="name" v-model="name" />
</form>
</template>
Schritt für Schritt, in Worten
  1. Das Formular hält den Namen im Zustand: name = ''. Das Feld auf der Seite zeigt, was der Zustand sagt, und der sagt: leer.
  2. Der Kunde drückt M. Der Browser setzt den Buchstaben ins Feld und löst das Ereignis input aus.
  3. v-model hört auf input. Der Text des Feldes ist jetzt „M“.
  4. v-model schreibt ihn in das ref: name.value = "M". Das ist der einzige Ort, an dem der Name lebt.
  5. Das ref hat sich geändert, also führt Vue das Template erneut aus. v-model gibt dem Feld den Wert von name: „M“.
  6. Das Feld bekommt seinen Wert vom Zustand. Es ist derselbe Buchstabe, aber jetzt entscheidet der Zustand, was das Feld zeigt.
  7. Jede Taste läuft den ganzen Kreis: Feld, Ereignis, Zustand, neues Zeichnen, Feld. Eine einzige Quelle der Wahrheit: der Zustand.
  8. Jetzt dasselbe Feld ohne v-model. Das Feld funktioniert, aber niemand hört es ab und niemand gibt ihm einen Wert.
  9. Der Kunde tippt „Maria“. Die Buchstaben bleiben im DOM. Der Zustand sagt immer noch: name = ''.
  10. Beim Absenden schickt der Code den Zustand: { name: '' }. Der Server antwortet „Der Name ist erforderlich“, während das Feld „Maria“ zeigt.
  11. Die Regel: Jedes Feld hat v-model, einen Wert aus dem Zustand und ein Ereignis zurück dorthin. Dann können Feld und Zustand nicht auseinanderlaufen.

Der Kreis sieht wie ein unnötiger Umweg aus: Der Buchstabe steht auch ohne ihn im Feld. Aber seinetwegen können Zustand und Feld nicht auseinanderlaufen.

Probier es aus

Binde die Felder

Das Feld Name, das Select Zahlung und die Checkbox Ich akzeptiere die Bedingungen sind nicht an den Zustand gebunden. Binde alle drei. Der Button Bestellen ist deaktiviert, bis die Bedingungen akzeptiert sind. Die Prüfung tippt einen Namen, eine E-Mail-Adresse und eine Adresse ein, wählt Karte bei Lieferung, setzt und entfernt das Häkchen und bestellt dann. Sie erwartet Bestellung Nr. 61 ist angenommen. und eine Anfrage mit dem eingetippten Namen und payment: 'card'.

Vue

Probier es aus

Ergänze die Lücke

Was gehört in die Lücke, damit die Checkbox gebunden ist?

<script setup>
const agree = ref(false);
</script>

<template>
  <input type="checkbox" ="agree" />
</template>

Probier es aus

Finde den Fehler

Der Kunde tippt seinen Namen, er erscheint, aber das Formular sagt, er sei leer. Die E-Mail funktioniert. Welche Zeile ist schuld?

Wähle die Zeile mit dem Fehler.

Die Idee

Eine Prüfung neben dem Feld

Der Server prüft jede Bestellung. Aber der Kunde soll nicht auf eine Antwort warten müssen, um zu erfahren, dass er die Adresse vergessen hat. Also prüft auch das Formular selbst, bevor es sendet.

  1. Wann. Beim Drücken auf „Bestellen“. Gibt es Fehler, geht keine Anfrage raus.
  2. Was. Eine Funktion check() gibt ein Objekt mit den Fehlern je Feld zurück: { name: 'Gib deinen Namen ein', email: … }. Ein leeres Objekt heißt „alles in Ordnung“. Es liegt im Zustand errors.
  3. Wo. Neben dem Feld, nicht in einer gemeinsamen Liste oben. Der Kunde sieht, welches Feld er korrigieren muss.
  4. Für alle. Ein Screenreader sieht nicht, dass der Text rot ist. Deshalb bekommt das Feld aria-invalid="true" (der Reader sagt „ungültig“), und die Meldung bekommt eine id, auf die das Feld mit aria-describedby zeigt. Kommt der Fokus ins Feld, liest der Reader auch die Meldung vor.
<input
id="email"
v-model="email"
:aria-invalid="!!errors.email"
:aria-describedby="errors.email ? 'email-error' : undefined"
/>
<p v-if="errors.email" id="email-error" class="error">{{ errors.email }}</p>

Das Formular hat novalidate. Ohne das stoppt der Browser das Absenden mit seinen eigenen Sprechblasen, bevor dein Code drankommt, und die beiden Prüfungen kommen sich in die Quere.

Der Server prüft noch einmal. Die Prüfung im Browser ist eine Bequemlichkeit für den Kunden. Sie schützt den Shop nicht: Der Code der Seite liegt beim Kunden, und eine Anfrage kann auch ohne ihn kommen. Warum das so ist, erklärt die Lektion „Nicht vertrauenswürdige Eingaben“.

Probier es aus

Prüfen vor dem Senden

Füge eine Prüfung vor dem Senden hinzu. Ein leerer Name ergibt Gib deinen Namen ein, eine E-Mail ohne @ ergibt Die E-Mail muss ein @ enthalten, eine leere Adresse ergibt Gib eine Lieferadresse ein. Jede Meldung steht neben ihrem Feld, in einem Element mit der id name-error, email-error und address-error, und das Feld hat aria-invalid=true und aria-describedby, das darauf zeigt. Bei Fehlern geht keine Anfrage raus. Die Prüfung akzeptiert die Bedingungen, bestellt mit einem leeren Formular, dann mit einer E-Mail ohne @ und zuletzt mit allem richtig.

Vue

Die Idee

Senden und die Antwort des Servers

Die Anfrage braucht Zeit, und der Kunde weiß nicht, ob sein Klick angekommen ist. Klickt er noch einmal, geht eine zweite Bestellung raus. Deshalb hat der Zustand auch sending:

  1. Vor fetch wird sending zu true. Der Button ist disabled, und daneben steht „Wird gesendet…“.
  2. Nach der Antwort wird sending zu false, egal was zurückkam.
  3. 201: Der Body ist { id }. Das Formular wird durch „Bestellung Nr. 61 ist angenommen.“ ersetzt.
  4. 400: Der Body ist { errors: { name, email, address, items } }, eine Meldung je Feld. Sie kommen in dasselbe errors und erscheinen so an denselben Stellen neben den Feldern. items hat kein Feld: Es erscheint neben der Zeile mit dem Warenkorb.
sending.value = true;
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name.value, email: email.value, /* … */ items: ITEMS }),
});
const data = await res.json();
sending.value = false;
if (res.ok) orderId.value = data.id;
else errors.value = data.errors;

Die Prüfung im Browser ist einfacher als die des Servers: Sie sucht nur nach „@“. Die E-Mail maria@shop besteht sie, und der Server schickt sie mit „Die E-Mail-Adresse ist ungültig“ zurück. Das Formular muss also auch einen Fehler zeigen können, den es nicht kannte.

Genauer, für Neugierige

Modifikatoren von v-model. v-model.trim entfernt die Leerzeichen am Anfang und am Ende, v-model.number macht aus dem Text eine Zahl (für type="number"), und v-model.lazy aktualisiert bei change (wenn das Feld den Fokus verliert), nicht bei jeder Taste.

:value ohne Ereignis. Das Feld zeigt name, die Buchstaben gehen ins DOM, aber name ändert sich nicht. Wird die Komponente aus einem anderen Grund erneut gezeichnet (etwa weil eine Fehlermeldung erscheint), setzt Vue das Feld auf den Wert von name zurück, und das Getippte verschwindet.

Ein Objekt statt fünf refs. Du kannst auch const form = reactive({ name: '', email: '', … }) mit v-model="form.name" haben. Vue beobachtet die Eigenschaften des Objekts, also funktioniert v-model an jeder von ihnen.

v-model an einer Komponente ist :modelValue nach unten und @update:modelValue nach oben, wie in der Lektion über das Hochreichen des Zustands. So entstehen Feld-Komponenten (<MoneyInput v-model="price" />).

Wann prüfen. Beim Absenden ist am einfachsten. Eine Prüfung bei jeder Taste zeigt Fehler, bevor der Kunde fertig ist („die E-Mail ist ungültig“ nach dem ersten Buchstaben). Die übliche Antwort: die erste Prüfung beim Absenden oder beim Verlassen des Feldes (blur), danach bei jeder Taste, damit der Fehler verschwindet, sobald er behoben ist.

Fokus auf einen Fehler. Nach einem fehlgeschlagenen Absenden hilft es, den Fokus auf das erste Feld mit einem Fehler zu setzen. Dann kommen Tastatur und Screenreader direkt zum Problem.

Die Prüfung sieht den Code nicht. Die Aufgaben hier prüfen das Verhalten: was angezeigt wird, was deaktiviert ist, was gesendet wird. Sie sehen nicht, ob die Prüfung in einer eigenen Funktion steht oder wie die Variablen heißen.

Übung

Die Bestellung, bis zum Ende

Jetzt prüft das Formular im Browser, aber es zeigt nicht, dass es sendet, und es legt die Fehler des Servers in einen einzigen Absatz. Mach daraus eine echte Bestellung: Während die Anfrage läuft, ist der Button Bestellen deaktiviert, und es gibt einen Text Wird gesendet… (mit role=status). Bei einer Antwort 400 kommen die Fehler aus data.errors neben die Felder, an dieselben Stellen wie deine (email-error und aria-invalid an der E-Mail), und der Fehler für items neben die Zeile mit dem Warenkorb. Die Prüfung tippt die E-Mail maria@shop ein: Sie besteht deine Prüfung, aber der Server schickt sie mit Die E-Mail-Adresse ist ungültig zurück. Dann hängt sie .bg an, und die Bestellung geht durch: Bestellung Nr. 61 ist angenommen.

Vue

Test

Teste dich

1. Das Feld „Name“ zeigt „Maria“, und der Zustand name ist leer. Was geht an den Server?
2. Das Feld ist <input :value="name" />, ohne v-model. Was passiert, wenn der Kunde tippt?
3. Was liefert v-model an einer Checkbox (type="checkbox")?
4. Die Prüfung im Browser sagt, die E-Mail sei in Ordnung. Soll der Server sie noch einmal prüfen?
5. Warum ist der Button „Bestellen“ deaktiviert, während die Anfrage läuft?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·