React-Formulare: Verwendung von Refs

Avatar of Loren Stewart
Loren Stewart am

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

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

  1. Sie sind einfach einzurichten, um Benutzereingaben zu validieren.
  2. 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.

  1. Texteingaben, Zahleneingaben und Auswahllisten
  2. Props von Kind zu Eltern weitergeben
  3. Radio-Sets
  4. 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

  1. Setzen Sie einen Ref auf das <form>-Tag (Zeile 20 unten).
  2. 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.
  3. 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

  1. Setzen Sie einen Ref auf das <form>-Tag (Zeile 27 unten).
  2. 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...
  3. Konvertieren Sie die Node-Liste in ein Array, damit Array-Methoden verfügbar sind (checkboxArray in Zeile 12).
  4. Verwenden Sie Array.filter(), um nur die angekreuzten Checkboxen zu erhalten (checkedCheckboxes in Zeile 15).
  5. Verwenden Sie Array.map(), um nur die Werte der angekreuzten Checkboxen zu behalten (checkedCheckboxesValues in 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

  1. den Wert eines Formularelements in Echtzeit überwachen (z. B. um nachfolgende Komponenten basierend auf Benutzereingaben zu rendern) oder
  2. 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.