Formularvalidierung in unter einer Stunde mit Vuelidate

Avatar of Sarah Drasner
Sarah Drasner am

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

Formularvalidierung hat den Ruf, knifflig zu implementieren zu sein. In diesem Tutorial werden wir die Dinge aufschlüsseln, um diesen Schmerz zu lindern. Die Erstellung schöner Abstraktionen für Formulare ist etwas, worin Vue.js hervorragend ist, und Vuelidate ist persönlich meine bevorzugte Option für Validierungen, da sie nicht viel Aufwand erfordert. Außerdem ist es wirklich flexibel, sodass wir es nicht einmal so machen müssen, wie ich es hier behandeln werde. Dies ist nur ein Ausgangspunkt.

Wenn Sie mein voll funktionsfähiges Beispiel einfach kopieren und einfügen möchten, finden Sie es am Ende. Nur zu. Ich werde es nicht verraten. Dann sind Ihre aufgewendeten Zeit definitiv unter einer Stunde und eher wie zwei Minuten, oder?! Ah, das Internet ist ein schöner Ort.

Sie stellen möglicherweise fest, dass Sie das Formular, das wir in diesem Beitrag verwenden, ändern müssen. In diesem Fall können Sie das Ganze lesen. Wir beginnen mit einem einfachen Fall und entwickeln nach und nach ein konkreteres Beispiel. Abschließend werden wir durchgehen, wie Formularfehler angezeigt werden, wenn der Benutzer das Formular ausgefüllt hat.

Einfachster Fall: Anzeige der Eingabe, sobald Sie mit der Eingabe fertig sind

Zuerst zeigen wir, wie wir mit Vuelidate arbeiten würden. Wir müssen ein Objekt namens validations erstellen, das die Datenstruktur dessen widerspiegelt, was wir im Formular erfassen möchten. Ganz einfach ausgedrückt sähe es so aus:

data: {
  name: ‘’
},
validations: {
  name: {
    required
  }
}

Dies würde ein Objekt innerhalb von Computed Properties erstellen, das wir mit $v finden können. In Vue DevTools sieht es so aus:

computed properties when empty

Ein paar Dinge sind hier zu beachten: $v ist eine Computed Property. Das ist großartig, denn das bedeutet, dass es bis zur Aktualisierung von etwas zwischengespeichert wird, was eine sehr performante Methode ist, um mit diesen Zustandsänderungen umzugehen. Wenn Sie mehr Hintergrund zu diesem Konzept wünschen, lesen Sie meinen Artikel hier.

Eine weitere Sache, die zu beachten ist: Es gibt zwei Objekte – ein allgemeines Objekt über alle Validierungen (derzeit nur eines hier) und ein spezifisches Objekt über die Eigenschaft name. Das ist großartig, denn wenn wir allgemeine Informationen über alle Felder suchen, haben wir diese Informationen. Und wenn wir spezifische Daten sammeln müssen, haben wir auch diese.

Werfen wir einen Blick darauf, was passiert, wenn wir anfangen, in dieses Eingabefeld zu tippen.

random typing shown in computed properties

Wir können in data sehen, dass wir... nun ja, mich selbst beim Tippen wie ein Verrückter haben. Aber schauen wir uns einige dieser anderen Felder an. $dirty bezieht sich in diesem Fall darauf, ob das Formular überhaupt berührt wurde. Wir können auch sehen, dass das Feld $model nun für das Namens-Objekt gefüllt ist, was dem entspricht, was in data steht.

$error und $invalid klingen gleich, sind aber tatsächlich ein wenig unterschiedlich. $invalid prüft, ob die Validierung bestanden wurde, aber $error prüft *sowohl* etwas, das $invalid ist, als auch, ob es $dirty ist (ob das Formular bereits berührt wurde oder nicht). Wenn all dies viel zu verarbeiten scheint (haha, verstehen Sie? Parse?), machen Sie sich keine Sorgen, wir werden viele dieser Teile Schritt für Schritt durchgehen.

Installation von Vuelidate und Erstellung unserer ersten Formularvalidierung

Okay, das war ein sehr einfaches Beispiel. Lassen Sie uns etwas Reales daraus machen. Wir bringen dies in unsere Anwendung und machen diesmal das Feld erforderlich *und* geben ihm eine Mindestlängenanforderung. In der Vue-App fügen wir zuerst Vuelidate hinzu.

yarn add vuelidate

Gehen wir nun zur Datei main.js und aktualisieren sie wie folgt:

import Vue from 'vue';
import Vuelidate from "vuelidate";
import App from './App.vue';
import store from './store';

Vue.use(Vuelidate);
Vue.config.productionTip = false

new Vue({
 store,
 render: h => h(App)
}).$mount('#app')

Importieren wir nun in der Komponente, die das Formularelement enthält, zuerst die benötigten Validatoren.

import { required, minLength } from 'vuelidate/lib/validators'

Dann packen wir die Daten in eine Funktion, damit wir die Komponente wiederverwenden können. Das wissen Sie wahrscheinlich schon. Als nächstes packen wir unser name-Formularfeld in ein Objekt, denn typischerweise möchten wir alle Formulardaten zusammen erfassen.

Wir müssen auch die Validierungen einschließen, die unsere Daten widerspiegeln werden. Wir verwenden required erneut, aber diesmal fügen wir auch ein Schlüssel-Wert-Paar für die Mindestlänge der Zeichen hinzu, minLength(x), was ungefähr so aussehen wird:

<script>
import { required, minLength } from 'vuelidate/lib/validators'

export default {
 data() {
   return {
     formResponses: {
       name: '',
     }
   }
 },
 validations: {
   formResponses: {
     name: {
       required,
        minLength: minLength(2)
     },
   }
 }
}
</script>

Als nächstes erstellen wir im Template ein Label aus Barrierefreiheitsgründen. Anstatt das zu verwenden, was in den Daten steht, um die Beziehung in v-model zu erstellen, verwenden wir die Computed Property ($model), die wir zuvor im validations-Objekt gesehen haben.

<template>
 <div id="app">
   <label for="fname">Name*</label>
   <input id="fname" class="full" v-model="$v.formResponses.name.$model" type="text">
 </div>
</template>

Schließlich platzieren wir unter dem Formulareingabefeld etwas Text unter dem Formular. Wir können required, angehängt an formResponses.name, verwenden, um zu sehen, ob es korrekt ausgewertet wird und ob es überhaupt vorhanden ist. Wir können auch sehen, ob mehr als die Mindestanzahl an Zeichen vorhanden ist. Wir haben sogar ein params-Objekt, das uns die angegebene Anzahl von Zeichen mitteilt. Wir werden all dies verwenden, um informative Fehlermeldungen für unseren Benutzer zu erstellen.

<p class="error" v-if="!$v.formResponses.name.required">this field is required</p>
<p class="error" v-if="!$v.formResponses.name.minLength">Field must have at least {{ $v.formResponses.name.$params.minLength.min }} characters.</p>

Und wir werden unsere Fehlerklasse so gestalten, dass Fehler auf einen Blick erkennbar sind.

.error {
  color: red;
}

Seien Sie ein wenig faul

Sie haben vielleicht im letzten Demo bemerkt, dass die Fehler sofort vorhanden sind und sich während der Eingabe aktualisieren. Ich persönlich zeige Formularvalidierungen nicht gerne so an, weil ich sie ablenkend und verwirrend finde. Was ich gerne tue, ist zu warten, bis die Eingabe abgeschlossen ist. Für diese Art von Interaktion ist Vue mit einem Modifier für v-model ausgestattet: v-model.lazy. Dies wertet die Zwei-Wege-Bindung erst aus, wenn der Benutzer die Aufgabe mit der Eingabe abgeschlossen hat.

Wir können unsere einzelne Formulareingabe nun wie folgt verbessern:

<label for="fname">Name*</label>
<input id="fname" class="full" v-model.lazy="$v.formResponses.name.$model" type="text">

Erstellung benutzerdefinierter Validatoren

Vuelidate kommt mit vielen Validatoren, was sehr hilfreich ist. Es gibt jedoch Zeiten, in denen wir etwas individuelleres benötigen. Machen wir einen benutzerdefinierten Validator für ein starkes Passwort und prüfen, ob es mit Vuelidates sameAs Validator übereinstimmt.

Als Erstes erstellen wir ein Label, das an eine Eingabe angehängt ist, und die Eingabe ist vom Typ password.

<section>
  <label for="fpass1">Password*</label>
  <input id="fpass1" v-model="$v.formResponses.password1.$model" type="password">
</section>

In unseren Daten erstellen wir password1 und password2 (die wir gleich zum Überprüfen übereinstimmender Passwörter verwenden werden) in unserem formResponses-Objekt und importieren, was wir von den Validatoren benötigen.

import { required, minLength, email, sameAs } from "vuelidate/lib/validators";

export default {
 data() {
   return {
     formResponses: {
       name: null,
       email: null,
       password1: null,
       password2: null
     }
   };
 },

Dann erstellen wir unseren benutzerdefinierten Validator. Im folgenden Code können Sie sehen, dass wir Regex für verschiedene Arten von Auswertungen verwenden. Wir erstellen eine strongPassword-Methode, übergeben unser password1 und können es dann auf verschiedene Weise mit .test() testen, was wie erwartet funktioniert: Es muss true zurückgeben, wenn es bestanden wird, und false, wenn nicht.

validations: {
  formResponses: {
    name: {
      required,
      minLength: minLength(3)
    },
    email: {
      required,
      email
    },
    password1: {
      required,
      strongPassword(password1) {
        return (
          /[a-z]/.test(password1) && // checks for a-z
          /[0-9]/.test(password1) && // checks for 0-9
          /\W|_/.test(password1) && // checks for special char
          password1.length >= 6
        );
      }
    },
 }

Ich trenne jede Zeile, damit Sie sehen können, was vor sich geht, aber wir könnten auch die gesamte Zeile als einzelne Zeile schreiben:

const regex = /^[a-zA-Z0-9!@#\$%\^\&*\)\(+=._-]{6,}$/g

Ich ziehe es vor, es aufzubrechen, weil es einfacher zu modifizieren ist.

Dies ermöglicht uns, den Fehlertext für unsere Validierung zu erstellen. Wir können ihn beliebig gestalten oder ihn sogar aus einem v-if herausnehmen und auf der Seite anzeigen. Ihnen überlassen!

<section>
  <label for="fpass1">Password*</label>
  <input id="fpass1" v-model="$v.formResponses.password1.$model" type="password">
  <p class="error" v-if="!$v.formResponses.password1.required">this field is required</p>
  <p class="error" v-if="!$v.formResponses.password1.strongPassword">Strong passwords need to have a letter, a number, a special character, and be more than 8 characters long.</p>
</section>

Nun können wir mit Vuelidates sameAs-Methode überprüfen, ob das zweite Passwort mit dem ersten übereinstimmt.

validations: {
  formResponses: {
    password1: {
      required,
      strongPassword(password1) {
        return (
          /[a-z]/.test(password1) && // checks for a-z
          /[0-9]/.test(password1) && // checks for 0-9
          /\W|_/.test(password1) && // checks for special char
          password1.length >= 6
        );
      }
    },
    password2: {
      required,
      sameAsPassword: sameAs("password1")
    }
  }
}

Und wir können unser zweites Passwortfeld erstellen:

<section>
  <label for="fpass2">Please re-type your Password</label>
  <input id="fpass2" v-model="$v.formResponses.password2.$model" type="password">
  <p class="error" v-if="!$v.formResponses.password2.required">this field is required</p>
  <p class="error" v-if="!$v.formResponses.password2.sameAsPassword">The passwords do not match.</p>
</section>

Nun können Sie das Ganze zusammen in Aktion sehen.

Auswertung nach Abschluss

Sie sehen, wie laut das letzte Beispiel ist, bis das Formular abgeschlossen ist. Meiner Meinung nach ist es besser, die Auswertung vorzunehmen, sobald das gesamte Formular abgeschlossen ist, damit der Benutzer nicht im Prozess unterbrochen wird. Hier ist, wie wir das machen können.

Erinnern Sie sich, als wir uns die Computed Properties angesehen haben, die $v enthielt? Es enthielt Objekte für alle einzelnen Eigenschaften, aber auch eines für alle Validierungen. Darin gab es drei sehr wichtige Werte:

  • $anyDirty: ob das Formular überhaupt berührt oder leer gelassen wurde
  • $invalid: ob es irgendwelche Fehler im Formular gibt
  • $anyError: Wenn es überhaupt Fehler gibt (auch nur einen), wird dies zu true ausgewertet.

Sie können $invalid verwenden, aber ich bevorzuge $anyError, da wir nicht auch prüfen müssen, ob es "dirty" ist.

Lassen Sie uns unser letztes Formular verbessern. Wir fügen einen Submit-Button und einen uiState-String hinzu, um den UI-Status zu verfolgen! Dies ist unglaublich nützlich, da wir verfolgen können, ob wir die Einreichung versucht haben und ob wir bereit sind, das Gesammelte zu senden. Wir werden auch eine kleine Stilverbesserung vornehmen: Platzieren Sie den Fehler im Formular so, dass er sich nicht verschiebt, um die Fehler anzuzeigen.

Zuerst fügen wir ein paar neue Daten-Eigenschaften hinzu:

data() {
  return {
    uiState: "submit not clicked",
    errors: false,
    empty: true,
    formResponses: {
      ...
    }
  }
}

Nun fügen wir am Ende des Formulars einen Submit-Button hinzu. Der Modifier .prevent am Ende der @click-Direktive verhält sich wie preventDefault und verhindert das Neuladen der Seite.

<section>
  <button @click.prevent="submitForm" class="submit">Submit</button>
</section>

Wir werden verschiedene Zustände in der submitForm-Methode behandeln. Wir werden diese Computed Property von Vuelidate ($anyDirty) verwenden, um zu sehen, ob das Formular leer ist. Denken Sie daran, dass wir diese Informationen aus this.$v abrufen können. Wir haben das formResponses-Objekt verwendet, um alle Formularantworten zu speichern. Daher werden wir this.$v.formResponses.$anyDirty verwenden. Wir werden diesen Wert unserer "leeren" Daten-Eigenschaft zuordnen. Wir tun dasselbe mit Fehlern und ändern den uiState zu "submit clicked".

submitForm() {
  this.formTouched = !this.$v.formResponses.$anyDirty;
  this.errors = this.$v.formResponses.$anyError;
  this.uiState = "submit clicked";
  if (this.errors === false && this.formTouched === false) {
    //this is where you send the responses
    this.uiState = "form submitted";
  }
}

Wenn das Formular keine Fehler hat und nicht leer ist, senden wir die Antworten und ändern auch den uiState auf "form submitted".

Nun können wir auch einige Zustände für Fehler und leere Zustände behandeln und schließlich, wenn das Formular übermittelt wurde, eine Erfolgsmeldung auswerten.

<section>
  <button @click.prevent="submitForm" class="submit">Submit</button>
  <p v-if="errors" class="error">The form above has errors,
    <br>please get your act together and resubmit
  </p>
  <p v-else-if="formTouched && uiState === 'submit clicked'" class="error">The form above is empty,
    <br>cmon y'all you can't submit an empty form!
  </p>
  <p v-else-if="uiState === 'form submitted'" class="success">Hooray! Your form was submitted!</p>
</section>

In diesem Formular haben wir jedem Abschnitt eine relative Position gegeben und unten etwas Abstand hinzugefügt. Dies ermöglicht uns, dem Fehlerzustand eine absolute Position zu geben, was verhindert, dass sich das Formular bewegt.

.error {
  color: red; 
  font-size: 12px;
  position: absolute;
  text-transform: uppercase;
}

Es gibt noch eine letzte Sache, die wir tun müssen: Da wir die Fehler nun absolut im Formular platziert haben, stapeln sie sich übereinander, es sei denn, wir platzieren sie stattdessen nebeneinander. Wir wollen auch prüfen, ob sich das Formular im Fehlerzustand befindet, was nur zutrifft, nachdem der Submit-Button geklickt wurde. Dies kann eine nützliche Methode sein – wir zeigen die Fehler erst an, wenn der Benutzer mit dem Formular fertig ist, was weniger invasiv sein kann. Es liegt an Ihnen, ob Sie dies so oder mit dem v-model.lazy-Beispiel aus früheren Abschnitten machen möchten.

Unsere bisherigen Fehler sahen so aus:

<section>
  ...
  <p class="error" v-if="!$v.formResponses.password2.required">this field is required</p>
  <p class="error" v-if="!$v.formResponses.password2.sameAsPassword">The passwords do not match.</p>
 </section>

Nun werden sie zusammen so gruppiert:

<p v-if="errors" class="error">
  <span v-if="!$v.formResponses.password1.required">this field is required.</span>
  <span v-if="!$v.formResponses.password1.strongPassword">Strong passwords need to have a letter, a number, a special character, and be more than 8 characters long.</span>
</p>

Um es Ihnen noch einfacher zu machen, gibt es eine Bibliothek, die dynamisch herausfindet, welcher Fehler angezeigt werden soll, basierend auf Ihrer Validierung. Super cool! Wenn Sie etwas Einfaches tun, ist es wahrscheinlich zu viel Overhead, aber wenn Sie ein wirklich komplexes Formular haben, kann es Ihnen Zeit sparen :)

Und da haben wir es! Unser Formular ist validiert und wir haben sowohl Fehler als auch leere Zustände, wenn wir sie brauchen, aber keine, während wir tippen.

Ein herzliches Dankeschön an Damian Dulisz, einen der Maintainer von Vuelidate, für die Korrektur dieses Artikels.