Erstellen Ihrer eigenen React-Validierungsbibliothek: Die Funktionen (Teil 2)
Veröffentlicht: 2022-03-10Die Implementierung einer Validierungsbibliothek ist gar nicht so schwer. Es werden auch nicht all diese zusätzlichen Funktionen hinzugefügt, die Ihre Validierungsbibliothek viel besser als die anderen machen.
In diesem Artikel wird die Implementierung der Validierungsbibliothek fortgesetzt, mit der wir im vorherigen Teil dieser Artikelserie begonnen haben. Dies sind die Funktionen, die uns von einem einfachen Proof of Concept zu einer tatsächlich nutzbaren Bibliothek führen werden!
- Teil 1: Die Grundlagen
- Teil 2: Die Funktionen
- Teil 3: Die Erfahrung
Validierung nur beim Senden anzeigen
Da wir alle Änderungsereignisse validieren, zeigen wir die Benutzerfehlermeldungen für eine gute Benutzererfahrung viel zu früh an. Es gibt einige Möglichkeiten, wie wir dies abmildern können.
Die erste Lösung besteht einfach darin, das submitted
Flag als zurückgegebene Eigenschaft des useValidation
. Auf diese Weise können wir überprüfen, ob das Formular gesendet wurde, bevor eine Fehlermeldung angezeigt wird. Der Nachteil hier ist, dass unser „Fehlercode anzeigen“ etwas länger wird:
<label> Username <br /> <input {...getFieldProps('username')} /> {submitted && errors.username && ( <div className="error">{errors.username}</div> )} </label>
Ein anderer Ansatz besteht darin, einen zweiten Satz von Fehlern (nennen wir sie submittedErrors
) bereitzustellen, der ein leeres Objekt ist, wenn „ submitted
“ falsch ist, und das „ errors
“-Objekt, wenn es „true“ ist. Wir können es so umsetzen:
const useValidation = config => { // as before return { errors: state.errors, submittedErrors: state.submitted ? state.errors : {}, }; }
Auf diese Weise können wir die Art der Fehler, die wir anzeigen möchten, einfach destrukturieren. Wir könnten dies natürlich auch auf der Aufrufseite tun – aber indem wir es hier bereitstellen, implementieren wir es nur einmal und nicht in allen Verbrauchern.
- Sehen Sie sich die CodeSandbox-Demo an, die zeigt, wie
submittedErrors
verwendet werden können.
Fehlermeldungen bei Unschärfe anzeigen
Viele Leute möchten, dass ein Fehler angezeigt wird, sobald sie ein bestimmtes Feld verlassen. Wir können dies unterstützen, indem wir nachverfolgen, welche Felder „verschwommen“ (wegnavigiert) wurden, und ein Objekt blurredErrors
, ähnlich wie bei den submittedErrors
oben.
Die Implementierung erfordert, dass wir einen neuen Aktionstyp handhaben – blur
, der ein neues Zustandsobjekt namens blurred
aktualisiert:
const initialState = { values: {}, errors: {}, blurred: {}, submitted: false, }; function validationReducer(state, action) { switch (action.type) { // as before case 'blur': const blurred = { ...state.blurred, [action.payload]: true }; return { ...state, blurred }; default: throw new Error('Unknown action type'); } }
Wenn wir die blur
-Aktion auslösen, erstellen wir eine neue Eigenschaft im blurred
-Zustandsobjekt mit dem Feldnamen als Schlüssel, der angibt, dass dieses Feld unkenntlich gemacht wurde.
Der nächste Schritt ist das Hinzufügen einer onBlur
-Prop zu unserer getFieldProps
Funktion, die diese Aktion gegebenenfalls auslöst:
getFieldProps: fieldName => ({ // as before onBlur: () => { dispatch({ type: 'blur', payload: fieldName }); }, }),
Schließlich müssen wir die blurredErrors
von unserem useValidation
Hook bereitstellen, damit wir die Fehler nur bei Bedarf anzeigen können.
const blurredErrors = useMemo(() => { const returnValue = {}; for (let fieldName in state.errors) { returnValue[fieldName] = state.blurred[fieldName] ? state.errors[fieldName] : null; } return returnValue; }, [state.errors, state.blurred]); return { // as before blurredErrors, };
Hier erstellen wir eine gespeicherte Funktion, die herausfindet, welche Fehler angezeigt werden sollen, je nachdem, ob das Feld verschwommen ist oder nicht. Wir berechnen diesen Satz von Fehlern immer dann neu, wenn sich die Fehler oder verschwommenen Objekte ändern. Weitere Informationen zum useMemo
Hook finden Sie in der Dokumentation.
- Siehe CodeSandbox-Demo
Zeit für einen kleinen Refactor
Unsere useValidation
Komponente gibt jetzt drei Fehlergruppen zurück – von denen die meisten irgendwann gleich aussehen werden. Anstatt diesen Weg zu gehen, lassen wir den Benutzer in der Konfiguration angeben, wann die Fehler in seinem Formular angezeigt werden sollen.
Unsere neue Option – showErrors
– akzeptiert entweder „submit“ (Standardeinstellung), „always“ oder „blur“. Bei Bedarf können wir später weitere Optionen hinzufügen.
function getErrors(state, config) { if (config.showErrors === 'always') { return state.errors; } if (config.showErrors === 'blur') { return Object.entries(state.blurred) .filter(([, blurred]) => blurred) .reduce((acc, [name]) => ({ ...acc, [name]: state.errors[name] }), {}); } return state.submitted ? state.errors : {}; } const useValidation = config => { // as before const errors = useMemo( () => getErrors(state, config), [state, config] ); return { errors, // as before }; };
Da der Fehlerbehandlungscode begonnen hat, den größten Teil unseres Platzes einzunehmen, wandeln wir ihn in eine eigene Funktion um. Wenn Sie den Object.entries
und .reduce
-Zeugs nicht folgen – das ist in Ordnung – es ist eine Umschreibung des for...in
-Codes im letzten Abschnitt.
Wenn wir eine onBlur- oder Instant-Validierung benötigen, können wir die showError
Prop in unserem useValidation
Konfigurationsobjekt angeben.
const config = { // as before showErrors: 'blur', }; const { getFormProps, getFieldProps, errors } = useValidation(config); // errors would now only include the ones that have been blurred
- Siehe CodeSandbox-Demo
Hinweis zu Annahmen
„Beachten Sie, dass ich jetzt davon ausgehe, dass jedes Formular Fehler auf die gleiche Weise anzeigen möchte (immer beim Absenden, immer bei Unschärfe usw.). Das mag für die meisten Anwendungen zutreffen, aber wahrscheinlich nicht für alle. Sich Ihrer Annahmen bewusst zu sein, ist ein großer Teil der Erstellung Ihrer API.“
Kreuzvalidierung zulassen
Eine wirklich leistungsstarke Funktion einer Validierungsbibliothek besteht darin, eine Kreuzvalidierung zu ermöglichen, dh die Validierung eines Felds auf den Wert eines anderen Felds zu stützen.
Um dies zu ermöglichen, müssen wir dafür sorgen, dass unser benutzerdefinierter Hook eine Funktion anstelle eines Objekts akzeptiert. Diese Funktion wird mit den aktuellen Feldwerten aufgerufen. Die Implementierung besteht eigentlich nur aus drei Codezeilen!
function useValidation(config) { const [state, dispatch] = useReducer(...); if (typeof config === 'function') { config = config(state.values); } }
Um diese Funktion zu verwenden, können wir einfach eine Funktion übergeben, die das Konfigurationsobjekt an useValidation
:
const { getFieldProps } = useValidation(fields => ({ password: { isRequired: { message: 'Please fill out the password' }, }, repeatPassword: { isRequired: { message: 'Please fill out the password one more time' }, isEqual: { value: fields.password, message: 'Your passwords don\'t match' } } }));
Hier verwenden wir den Wert von fields.password
, um sicherzustellen, dass zwei Passwortfelder dieselbe Eingabe enthalten (was eine schreckliche Benutzererfahrung ist, aber das ist für einen anderen Blogbeitrag).
- Siehe CodeSandbox-Demo, bei der Benutzername und Passwort nicht denselben Wert haben.
Fügen Sie einige Zugänglichkeitsgewinne hinzu
Wenn Sie für die Requisiten eines Feldes verantwortlich sind, ist es eine nette Sache, standardmäßig die richtigen Arien-Tags hinzuzufügen. Dies hilft Screenreadern bei der Erklärung Ihres Formulars.
Eine sehr einfache Verbesserung besteht darin, aria-invalid="true"
hinzuzufügen, wenn das Feld einen Fehler enthält. Lassen Sie uns das implementieren:
const useValidation = config => { // as before return { // as before getFieldProps: fieldName => ({ // as before 'aria-invalid': String(!!errors[fieldName]), }), } };
Das ist eine zusätzliche Codezeile und eine viel bessere Benutzererfahrung für Screenreader-Benutzer.
Sie fragen sich vielleicht, warum wir String(!!state.errors[fieldName])
schreiben? state.errors[fieldName]
ist eine Zeichenfolge, und der doppelte Negationsoperator gibt uns einen booleschen Wert (und nicht nur einen wahren oder falschen Wert). Die aria-invalid
Eigenschaft sollte jedoch ein String sein (sie kann neben „true“ oder „false“ auch „Grammatik“ oder „Rechtschreibung“ lesen), also müssen wir diesen booleschen Wert in sein String-Äquivalent zwingen.
Es gibt noch ein paar weitere Optimierungen, die wir vornehmen könnten, um die Zugänglichkeit zu verbessern, aber das scheint ein fairer Anfang zu sein.
Syntax der Kurzschrift-Validierungsnachricht
Die meisten Validatoren im calidators
-Paket (und die meisten anderen Validatoren, nehme ich an) erfordern nur eine Fehlermeldung. Wäre es nicht schön, wenn wir diese Zeichenfolge einfach anstelle eines Objekts mit einer message
, die diese Zeichenfolge enthält, übergeben könnten?
Lassen Sie uns das in unserer validateField
-Funktion implementieren:
function validateField(fieldValue = '', fieldConfig, allFieldValues) { for (let validatorName in fieldConfig) { let validatorConfig = fieldConfig[validatorName]; if (typeof validatorConfig === 'string') { validatorConfig = { message: validatorConfig }; } const configuredValidator = validators[validatorName](validatorConfig); const errorMessage = configuredValidator(fieldValue); if (errorMessage) { return errorMessage; } } return null; }
Auf diese Weise können wir unsere Validierungskonfiguration wie folgt umschreiben:
const config = { username: { isRequired: 'The username is required', isEmail: 'The username should be a valid email address', }, };
Viel sauberer!
Anfängliche Feldwerte
Manchmal müssen wir ein bereits ausgefülltes Formular validieren. Unser benutzerdefinierter Hook unterstützt das noch nicht – also packen wir es an!
Anfangsfeldwerte werden in der Konfiguration für jedes Feld in der Eigenschaft initialValue
. Wenn es nicht angegeben ist, ist es standardmäßig eine leere Zeichenfolge.
Wir werden eine Funktion getInitialState
erstellen, die den Anfangszustand unseres Reducers für uns erstellt.
function getInitialState(config) { if (typeof config === 'function') { config = config({}); } const initialValues = {}; const initialBlurred = {}; for (let fieldName in config.fields) { initialValues[fieldName] = config.fields[fieldName].initialValue || ''; initialBlurred[fieldName] = false; } const initialErrors = validateFields(initialValues, config.fields); return { values: initialValues, errors: initialErrors, blurred: initialBlurred, submitted: false, }; }
Wir gehen alle Felder durch, prüfen, ob sie eine initialValue
-Eigenschaft haben, und setzen den Anfangswert entsprechend. Dann lassen wir diese Anfangswerte durch die Validatoren laufen und berechnen auch die Anfangsfehler. Wir geben das Anfangszustandsobjekt zurück, das dann an unseren useReducer
Hook übergeben werden kann.
Da wir eine Nicht-Validator-Prop in die Feldkonfiguration einführen, müssen wir sie überspringen, wenn wir unsere Felder validieren. Dazu ändern wir unsere validateField
Funktion:
function validateField(fieldValue = '', fieldConfig) { const specialProps = ['initialValue']; for (let validatorName in fieldConfig) { if (specialProps.includes(validatorName)) { continue; } // as before } }
Da wir weitere Funktionen wie diese hinzufügen, können wir sie unserem specialProps
Array hinzufügen.
- Siehe CodeSandbox-Demo
Zusammenfassen
Wir sind auf dem besten Weg, eine erstaunliche Validierungsbibliothek zu erstellen. Wir haben Tonnen von Funktionen hinzugefügt und sind inzwischen ziemlich viel Vordenker.
Im nächsten Teil dieser Serie werden wir all diese Extras hinzufügen, die unsere Validierungsbibliothek sogar zum Trend auf LinkedIn machen.