React bietet zwei Standardmethoden, um Werte aus <form>-Elementen abzurufen. Die erste Methode ist die Implementierung sogenannter kontrollierter Komponenten (siehe meinen Blogbeitrag zu diesem Thema) und die zweite ist die Verwendung der ref-Eigenschaft von React.
Kontrollierte Komponenten sind aufwendig. Das definierende Merkmal einer kontrollierten Komponente ist, dass der angezeigte Wert an den Zustand der Komponente gebunden ist. Um den Wert zu aktualisieren, rufen Sie eine Funktion auf, die an den onChange-Ereignishandler des Formularelements angehängt ist. Die onChange-Funktion aktualisiert die Zustandseigenschaft, was wiederum den Wert des Formularelements aktualisiert.
(Bevor wir zu weit gehen, wenn Sie nur die Codebeispiele für diesen Artikel sehen möchten: hier entlang!)
Hier ist ein Beispiel für eine kontrollierte Komponente
import React, { Component } from 'react';
class ControlledCompExample extends Component {
constructor() {
super();
this.state = {
fullName: ''
}
}
handleFullNameChange = (e) => {
this.setState({
fullName: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
console.log(this.state.fullName)
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor="fullName">Full Name</label>
<input
type="text"
value={this.state.fullName}
onChange={this.handleFullNameChange}
name="fullName" />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default ControlledCompExample;
Der Wert des Eingabefelds ist this.state.fullName (Zeilen 7 und 26). Die onChange-Funktion ist handleFullNameChange (Zeilen 10–14 und Zeile 27).
Die Hauptvorteile von kontrollierten Komponenten sind
- Sie sind einfach einzurichten, um Benutzereingaben zu validieren.
- Sie können andere Komponenten dynamisch rendern, basierend auf dem Wert der kontrollierten Komponente. Beispielsweise kann der vom Benutzer aus einer Dropdownliste ausgewählte Wert (z. B. „Hund“ oder „Katze“) steuern, welche anderen Formular-Komponenten (z. B. eine Auswahl von Rassen als Checkboxen) im Formular gerendert werden.
Der Nachteil von kontrollierten Komponenten ist der Code-Aufwand. Sie benötigen eine Zustandseigenschaft, die als props an das Formularelement übergeben wird, und eine Funktion, um den Wert dieser Eigenschaft zu aktualisieren.
Für ein einzelnes Formularelement ist dies kein Problem – aber wenn Sie ein großes, komplexes Formular haben (das keine dynamische Anzeige oder Echtzeit-Validierung benötigt), werden Sie feststellen, dass Sie viel Code schreiben müssen, wenn Sie kontrollierte Komponenten übermäßig verwenden.
Eine einfachere und weniger aufwendige Methode, Werte aus einem Formularelement abzurufen, ist die Verwendung der ref-Eigenschaft. Verschiedene Formularelemente und Komponenten-Zusammensetzungen erfordern unterschiedliche Strategien, daher ist der Rest dieses Beitrags in die folgenden Abschnitte unterteilt.
- Texteingaben, Zahleneingaben und Auswahllisten
- Props von Kind zu Eltern weitergeben
- Radio-Sets
- Checkbox-Sets
1. Texteingaben, Zahleneingaben und Auswahllisten
Text- und Zahleneingaben bieten das einfachste Beispiel für die Verwendung von refs. Im ref-Attribut des Eingabefelds fügen Sie eine Pfeilfunktion hinzu, die die Eingabe als Argument erhält. Ich benenne das Argument gerne gleich wie das Element selbst, wie in Zeile 3 unten zu sehen ist
<input
type="text"
ref={input => this.fullName = input} />
Da es sich um einen Alias für das Eingabefeld selbst handelt, können Sie dem Argument beliebigen Namen geben
<input
type="number"
ref={cashMoney => this.amount = cashMoney} />
Anschließend nehmen Sie das Argument und weisen es einer Eigenschaft zu, die an das this-Schlüsselwort der Klasse angehängt ist. Die Eingabefelder (d. h. der DOM-Knoten) sind nun als this.fullName und this.amount zugänglich. Die Werte der Eingabefelder sind als this.fullName.value und this.amount.value zugänglich.
Dieselbe Strategie funktioniert für Select-Elemente (d. h. Dropdown-Listen).
<select
ref={select => this.petType = select}
name="petType">
<option value="cat">Cat</option>
<option value="dog">Dog</option>
<option value="ferret">Ferret</option>
</select>
Der ausgewählte Wert ist als this.petType.value zugänglich.
2. Weitergabe von Props von Kind zu Eltern
Bei einer kontrollierten Komponente ist das Abrufen des Werts von einer Kind-Komponente zu einer Eltern-Komponente unkompliziert – der Wert existiert bereits in der Eltern-Komponente! Er wird an das Kind weitergegeben. Eine onChange-Funktion wird ebenfalls weitergegeben und aktualisiert den Wert, während der Benutzer mit der Benutzeroberfläche interagiert.
Dies können Sie in den Beispielen für kontrollierte Komponenten in meinem vorherigen Beitrag sehen.
Während der Wert bei kontrollierten Komponenten bereits im Zustand der Eltern-Komponente existiert, ist dies bei der Verwendung von refs nicht der Fall. Bei refs befindet sich der Wert im DOM-Knoten selbst und muss *nach oben* zur Eltern-Komponente kommuniziert werden.
Um diesen Wert von Kind zu Eltern weiterzugeben, muss die Eltern-Komponente dem Kind eine „Hook“, wenn man so will, weitergeben. Das Kind hängt dann einen Knoten an den „Hook“, damit die Eltern-Komponente darauf zugreifen kann.
Betrachten wir den Code, bevor wir dies weiter besprechen.
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
console.log('first name:', this.firstName.value);
this.firstName.value = 'Got ya!';
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<CustomInput
label={'Name'}
firstName={input => this.firstName = input} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
function CustomInput(props) {
return (
<div>
<label>{props.label}:</label>
<input type="text" ref={props.firstName}/>
</div>
);
}
export default RefsForm;
Oben sehen Sie eine Formular-Komponente RefForm und eine Eingabe-Komponente namens CustomInput. Normalerweise befindet sich die Pfeilfunktion auf dem Eingabefeld selbst, aber hier wird sie als Prop weitergegeben (siehe Zeilen 15 und 27). Da die Pfeilfunktion in der Eltern-Komponente residiert, gehört das this von this.firstName zur Eltern-Komponente.
Der Wert des Kind-Eingabefelds wird der Eigenschaft this.firstName der Eltern-Komponente zugewiesen, sodass der Wert des Kindes für die Eltern-Komponente verfügbar ist. Nun verweist this.firstName in der Eltern-Komponente auf einen DOM-Knoten in der Kind-Komponente (d. h. die Eingabe in CustomInput).
Nicht nur der DOM-Knoten der Eingabe kann von der Eltern-Komponente *zugegriffen* werden, sondern der Wert des Knotens kann auch von innerhalb der Eltern-Komponente *zugewiesen* werden. Dies wird oben in Zeile 7 gezeigt. Sobald das Formular abgeschickt wird, wird der Wert der Eingabe auf „Got ya!“ gesetzt.
Dieses Muster ist ein wenig verwirrend, also starren Sie es eine Weile an und spielen Sie mit dem Code herum, bis es Sinn ergibt.
Es ist möglicherweise besser, Radios und Checkboxen als kontrollierte Komponenten zu erstellen, aber wenn Sie wirklich refs verwenden möchten, sind die nächsten beiden Abschnitte für Sie.
3. Radio-Sets
Im Gegensatz zu Text- und Zahleneingaben kommen Radios in Sets vor. Jedes Element in einem Set hat den gleichen name-Attribut, wie hier:
<form>
<label>
Cat
<input type="radio" value="cat" name="pet" />
</label>
<label>
Dog
<input type="radio" value="dog" name="pet" />
</label>
<label>
Ferret
<input type="radio" value="ferret" name="pet" />
</label>
<input type="submit" value="Submit" />
</form>
Es gibt drei Optionen im „Haustier“-Radio-Set: „Katze“, „Hund“ und „Frettchen“.
Da das gesamte Set unser Anliegen ist, ist das Setzen eines ref auf jeden Radio-Input nicht ideal. Und leider gibt es keinen DOM-Knoten, der ein Set von Radios kapselt.
Das Abrufen des Werts des Radio-Sets kann durch drei Schritte erfolgen
- Setzen Sie einen Ref auf das
<form>-Tag (Zeile 20 unten). - Extrahieren Sie das Set von Radios aus dem Formular. In diesem Fall ist es das
pet-Set (Zeile 9 unten).- Hier werden eine Node-Liste und ein Wert zurückgegeben. In diesem Fall enthält diese Node-Liste drei Input-Knoten und den ausgewählten Wert.
- Denken Sie daran, dass eine Node-Liste wie ein Array aussieht, aber keins ist und Array-Methoden fehlen. Mehr zu diesem Thema im nächsten Abschnitt.
- Greifen Sie über Punktnotation auf den Wert des Sets zu (Zeile 13 unten).
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
// extract the node list from the form
// it looks like an array, but lacks array methods
const { pet } = this.form;
// a set of radios has value property
// checkout out the log for proof
console.log(pet, pet.value);
}
render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<label>
Cat
<input type="radio" value="cat" name="pet" />
</label>
<label>
Dog
<input type="radio" value="dog" name="pet" />
</label>
<label>
Ferret
<input type="radio" value="ferret" name="pet" />
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default RefsForm;
Dies funktioniert auch, wenn Sie ein Formular aus Kindkomponenten zusammensetzen. Obwohl es mehr Logik in den Komponenten gibt, bleibt die Technik zum Abrufen des Werts aus dem Radio-Set gleich.
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
// extract the node list from the form
// it looks like an array, but lacks array methods
const { pet } = this.form;
// a set of radios has value property
// checkout out the log for proof
console.log(pet, pet.value);
}
render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<RadioSet
setName={'pet'}
setOptions={['cat', 'dog', 'ferret']} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
function RadioSet(props) {
return (
<div>
{props.setOptions.map(option => {
return (
<label
key={option}
style={{textTransform: 'capitalize'}}>
{option}
<input
type="radio"
value={option}
name={props.setName} />
</label>
)
})}
</div>
);
}
export default RefsForm;
4. Checkbox-Sets
Im Gegensatz zu einem Radio-Set kann ein Checkbox-Set mehrere ausgewählte Werte haben. Dies macht das Extrahieren dieser Werte etwas komplizierter als das Extrahieren des Werts eines Radio-Sets.
Das Abrufen der ausgewählten Werte des Checkbox-Sets kann durch diese fünf Schritte erfolgen
- Setzen Sie einen Ref auf das
<form>-Tag (Zeile 27 unten). - Extrahieren Sie das Set von Checkboxen aus dem Formular. In diesem Fall ist es das
pet-Set (Zeile 9).- Hier werden eine Node-Liste und ein Wert zurückgegeben.
- Denken Sie daran, dass eine Node-Liste wie ein Array aussieht, aber keins ist und Array-Methoden fehlen, was uns zum nächsten Schritt bringt...
- Konvertieren Sie die Node-Liste in ein Array, damit Array-Methoden verfügbar sind (
checkboxArrayin Zeile 12). - Verwenden Sie
Array.filter(), um nur die angekreuzten Checkboxen zu erhalten (checkedCheckboxesin Zeile 15). - Verwenden Sie
Array.map(), um nur die Werte der angekreuzten Checkboxen zu behalten (checkedCheckboxesValuesin Zeile 19).
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
// extract the node list from the form
// it looks like an array, but lacks array methods
const { pet } = this.form;
// convert node list to an array
const checkboxArray = Array.prototype.slice.call(pet);
// extract only the checked checkboxes
const checkedCheckboxes = checkboxArray.filter(input => input.checked);
console.log('checked array:', checkedCheckboxes);
// use .map() to extract the value from each checked checkbox
const checkedCheckboxesValues = checkedCheckboxes.map(input => input.value);
console.log('checked array values:', checkedCheckboxesValues);
}
render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<label>
Cat
<input type="checkbox" value="cat" name="pet" />
</label>
<label>
Dog
<input type="checkbox" value="dog" name="pet" />
</label>
<label>
Ferret
<input type="checkbox" value="ferret" name="pet" />
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default RefsForm;
Die Verwendung einer Checkbox-Set-Kindkomponente funktioniert genauso wie das Radio-Set-Beispiel im vorherigen Abschnitt.
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
// extract the node list from the form
// it looks like an array, but lacks array methods
const { pet } = this.form;
// convert node list to an array
const checkboxArray = Array.prototype.slice.call(pet);
// extract only the checked checkboxes
const checkedCheckboxes = checkboxArray.filter(input => input.checked);
console.log('checked array:', checkedCheckboxes);
// use .map() to extract the value from each checked checkbox
const checkedCheckboxesValues = checkedCheckboxes.map(input => input.value);
console.log('checked array values:', checkedCheckboxesValues);
}
render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<CheckboxSet
setName={'pet'}
setOptions={['cat', 'dog', 'ferret']} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
function CheckboxSet(props) {
return (
<div>
{props.setOptions.map(option => {
return (
<label
key={option}
style={{textTransform: 'capitalize'}}>
{option}
<input
type="checkbox"
value={option}
name={props.setName} />
</label>
)
})}
</div>
);
}
export default RefsForm;
Fazit
Wenn Sie nicht müssen
- den Wert eines Formularelements in Echtzeit überwachen (z. B. um nachfolgende Komponenten basierend auf Benutzereingaben zu rendern) oder
- benutzerdefinierte Validierungen in Echtzeit durchführen,
dann ist die Verwendung von refs zum Abrufen von Daten aus Formularelementen eine gute Wahl.
Der Hauptvorteil der Verwendung von refs gegenüber kontrollierten Komponenten ist, dass Sie in den meisten Fällen weniger Code schreiben werden. Der Ausnahmefall sind Checkbox-Sets (und Radios in geringerem Maße). Für Checkbox-Sets ist der Code-Aufwand, den Sie durch die Verwendung von Refs sparen, minimal, sodass es weniger klar ist, ob eine kontrollierte Komponente oder refs verwendet werden sollen.
Tolle Erklärung zur Verwendung von kontrollierten Komponenten und Refs. Ich habe zwei Gedanken
A) In Punkt Nr. 2, warum sollte
RefsFormauf ein Kind vonCustomInputzugreifen können? Das sieht nach einem React-Antipattern aus. Stattdessen sollteCustomInputeine Prop bereitstellen, die als Callback fungiert, wenn sich ihr Wert ändert, auf denRefsFormhören kann, um den aktualisierten Wert zu erhalten.B) Es gibt andere, normalerweise sauberere Techniken, um auf unkontrollierte Eingaben ohne ein Ref pro Element zuzugreifen
Nur zur Klarstellung: Die letzten beiden Methoden – die Verwendung von
FormData(eingebaut) undformSerialize(npm-Paket) – behandeln Radiobuttons und Checkboxen out-of-the-box korrekt. Die erste Methode erfordert eine manuelle Behandlung von Radiobuttons und Checkboxen (gemäß Punkt #3 im Artikel).Eine Alternative zur Verwendung von Refs für Formulare wäre die Verwendung des onSubmit-Handlers mit der FormData-Klasse (verfügbar in allen seriösen Browsern und Polyfills)
Obwohl ich kein Experte für React bin, fühlten sich Refs für mich schon immer eher wie eine Fluchttür an, um mit Bibliotheken oder Code umzugehen, der nicht sauber in das virtuelle DOM von React passt
Seien Sie vorsichtig mit
FormDataund seinen Gettern (entries(),getAll()usw.). Während grundlegendeFormData-Funktionalität in den meisten Browsern verfügbar ist (z. B. die Übergabe vonFormDataalsfetch()-Body), sind seine Getter nicht in Safari (!), IE und Edge (!) verfügbar.Ich empfehle die Verwendung von form-serialize.
Wir müssen nur auf den Formularabsendung warten und sobald das Formular abgeschickt ist, übergeben wir die Formularinstanz an form-serialize, um ein JSON-Objekt zu erhalten.
Durch die Verwendung von form-serialize können wir kontrollierte Eingaben vermeiden und müssen nicht jedes Mal Refs schreiben, wenn wir eine Eingabe hinzufügen.
Das ist hilfreich! JSX kontrolliert Arrays besser. Das ist meine Erkenntnis :)
Wenn Sie viele Eingaben haben, könnte die direkte Zuweisung aller Refs an
thiszu Kopfschmerzen führen. Es könnte einfacher sein, sie in einer Sammlung zu organisieren, z. B.this.fields<input
type="number"
ref={cashMoney => this.fields.amount = cashMoney} />
Dann können Sie über
this.fieldsmit einerfor inSchleife iterieren, um auf alle Felder zuzugreifen, falls Sie dies benötigen. Außerdem, wenn Sie einethis.amount-Eigenschaft auf der Komponente für andere Zwecke beibehalten müssen, wird ein<input name=amount>-Element nicht damit kollidieren.Die Vermeidung von Refs wird empfohlen, wenn dies deklarativ erreicht werden kann. Es gibt nur wenige Anwendungsfälle, die Facebook für Refs empfiehlt, richtig?