Verständnis von React `setState`

Avatar of Kingsley Silas
Kingsley Silas am

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

React-Komponenten können und haben oft Zustand. Der Zustand kann alles Mögliche sein, aber denken Sie an Dinge wie, ob ein Benutzer angemeldet ist oder nicht und die Anzeige des richtigen Benutzernamens basierend darauf, welches Konto aktiv ist. Oder ein Array von Blog-Posts. Oder ob ein Modal geöffnet ist oder nicht und welcher Tab darin aktiv ist.

React-Komponenten mit Zustand rendern eine UI basierend auf diesem Zustand. Wenn sich der Zustand von Komponenten ändert, ändert sich auch die UI der Komponente.

Das macht das Verständnis dafür, wann und wie der Zustand Ihrer Komponente geändert werden muss, wichtig. Am Ende dieses Tutorials sollten Sie wissen, wie setState funktioniert, und in der Lage sein, häufige Fallstricke zu vermeiden, auf die viele von uns beim Erlernen von React stoßen.

Funktionsweise von `setState()`

setState() ist der einzige legitime Weg, den Zustand nach der anfänglichen Zustandseinstellung zu aktualisieren. Nehmen wir an, wir haben eine Suchkomponente und möchten den gesuchten Begriff anzeigen, den ein Benutzer eingibt.

Hier ist die Einrichtung

import React, { Component } from 'react'

class Search extends Component {
  constructor(props) {
    super(props)

    state = {
      searchTerm: ''
    }
  }
}

Wir übergeben einen leeren String als Wert und müssen setState() aufrufen, um den Zustand von searchTerm zu aktualisieren.

setState({ searchTerm: event.target.value })

Hier übergeben wir ein Objekt an setState(). Das Objekt enthält den Teil des Zustands, den wir aktualisieren möchten, in diesem Fall den Wert von searchTerm. React nimmt diesen Wert und fügt ihn in das Objekt ein, das ihn benötigt. Es ist so ähnlich, als würde die Search-Komponente fragen, was sie für den Wert von searchTerm verwenden soll, und setState() antwortet mit einer Antwort.

Dies ist im Grunde der Beginn eines Prozesses, den React Reconciliation nennt. Der Reconciliation-Prozess ist die Art und Weise, wie React das DOM aktualisiert, indem Änderungen an der Komponente vorgenommen werden, basierend auf der Zustandsänderung. Wenn die Anforderung an setState() ausgelöst wird, erstellt React einen neuen Baum, der die reaktiven Elemente in der Komponente (zusammen mit dem aktualisierten Zustand) enthält. Dieser Baum wird verwendet, um herauszufinden, wie sich die UI der Search-Komponente als Reaktion auf die Zustandsänderung ändern sollte, indem er mit den Elementen des vorherigen Baums verglichen wird. React weiß, welche Änderungen implementiert werden müssen, und aktualisiert nur die Teile des DOM, wo es notwendig ist. Deshalb ist React schnell.

Das klingt nach viel, aber um den Ablauf zusammenzufassen

  • Wir haben eine Suchkomponente, die einen Suchbegriff anzeigt
  • Dieser Suchbegriff ist derzeit leer
  • Der Benutzer gibt einen Suchbegriff ein
  • Dieser Begriff wird erfasst und von setState als Wert gespeichert
  • Reconciliation findet statt und React bemerkt die Wertänderung
  • React weist die Suchkomponente an, den Wert zu aktualisieren, und der Suchbegriff wird übernommen

Der Reconciliation-Prozess ändert nicht notwendigerweise den gesamten Baum, außer in Situationen, in denen die Wurzel des Baums wie hier geändert wird

// old
<div>
  <Search />
</div>

// new
<span>
  <Search />
</span>

Alle <div>-Tags werden zu <span>-Tags und der gesamte Komponentenbaum wird dadurch aktualisiert.

Die Faustregel lautet: Verändern Sie den Zustand niemals direkt. Verwenden Sie immer setState(), um den Zustand zu ändern. Das direkte Ändern des Zustands, wie im folgenden Schnipsel, bewirkt nicht, dass die Komponente neu gerendert wird.

// do not do this
this.state = {
  searchTerm: event.target.value
}

Übergeben einer Funktion an `setState()`

Um diese Idee weiter zu demonstrieren, erstellen wir einen einfachen Zähler, der bei jedem Klick inkrementiert und dekrementiert.

Siehe den Pen setState Pen von Kingsley Silas Chijioke (@kinsomicrote) auf CodePen.

Lassen Sie uns die Komponente registrieren und das Markup für die UI definieren

class App extends React.Component {

state = { count: 0 }

handleIncrement = () => {
  this.setState({ count: this.state.count + 1 })
}

handleDecrement = () => {
  this.setState({ count: this.state.count - 1 })
}
  render() {
    return (
      <div>
        <div>
          {this.state.count}
        </div>
        <button onClick={this.handleIncrement}>Increment by 1</button>
        <button onClick={this.handleDecrement}>Decrement by 1</button>
      </div>
    )
  }
}

An diesem Punkt inkrementiert oder dekrementiert der Zähler bei jedem Klick einfach den Zähler um 1.

Aber was, wenn wir stattdessen um 3 inkrementieren oder dekrementieren wollten? Wir könnten versuchen, setState() dreimal in den Funktionen handleDecrement und handleIncrement aufzurufen, wie hier

handleIncrement = () => {
  this.setState({ count: this.state.count + 1 })
  this.setState({ count: this.state.count + 1 })
  this.setState({ count: this.state.count + 1 })
}

handleDecrement = () => {
  this.setState({ count: this.state.count - 1 })
  this.setState({ count: this.state.count - 1 })
  this.setState({ count: this.state.count - 1 })
}

Wenn Sie zu Hause mitprogrammieren, sind Sie vielleicht überrascht festzustellen, dass das nicht funktioniert.

Der obige Codeausschnitt entspricht

Object.assign(  
  {},
  { count: this.state.count + 1 },
  { count: this.state.count + 1 },
  { count: this.state.count + 1 },
)

Object.assign() wird verwendet, um Daten von einem Quellobjekt in ein Zielobjekt zu kopieren. Wenn die von der Quelle zum Ziel kopierten Daten alle die gleichen Schlüssel haben, wie in unserem Beispiel, gewinnt das letzte Objekt. Hier ist eine einfachere Version, wie Object.assign() funktioniert;

let count = 3

const object = Object.assign({}, 
  {count: count + 1}, 
  {count: count + 2}, 
  {count: count + 3}
);

console.log(object);
// output: Object { count: 6 }

Anstatt also, dass der Aufruf dreimal erfolgt, geschieht er nur einmal. Dies kann behoben werden, indem eine Funktion an setState() übergeben wird. Genau wie Sie Objekte an setState() übergeben, können Sie auch Funktionen übergeben, und das ist der Ausweg aus der obigen Situation.

Wenn wir die Funktion handleIncrement so bearbeiten, dass sie so aussieht

handleIncrement = () => {
  this.setState((prevState) => ({ count: prevState.count + 1 }))
  this.setState((prevState) => ({ count: prevState.count + 1 }))
  this.setState((prevState) => ({ count: prevState.count + 1 }))
}

…können wir jetzt mit einem Klick dreimal inkrementieren.

In diesem Fall, anstatt zu verschmelzen, reiht React die Funktionsaufrufe in der Reihenfolge, in der sie gemacht werden, in eine Warteschlange ein und aktualisiert den gesamten Zustand, sobald er fertig ist. Dies aktualisiert den Zustand von count auf 3 statt auf 1.

Zugriff auf vorherigen Zustand über Updater

Beim Erstellen von React-Anwendungen werden Sie manchmal den Zustand basierend auf dem vorherigen Zustand der Komponente berechnen wollen. Sie können sich nicht immer auf this.state verlassen, um den korrekten Zustand unmittelbar nach dem Aufruf von setState() zu enthalten, da dieser immer gleich dem auf dem Bildschirm gerenderten Zustand ist.

Kehren wir zu unserem Zählerbeispiel zurück, um zu sehen, wie das funktioniert. Nehmen wir an, wir haben eine Funktion, die unseren Zähler um 1 dekrementiert. Diese Funktion sieht so aus

changeCount = () => {
  this.setState({ count: this.state.count - 1})
}

Was wir wollen, ist die Möglichkeit, um 3 zu dekrementieren. Die Funktion changeCount() wird dreimal in einer Funktion aufgerufen, die das Klick-Ereignis behandelt, wie hier.

handleDecrement = () => {
  this.changeCount()
  this.changeCount()
  this.changeCount()
}

Jedes Mal, wenn der Button zum Dekrementieren geklickt wird, dekrementiert sich der Zähler stattdessen um 1 statt um 3. Das liegt daran, dass this.state.count erst aktualisiert wird, wenn die Komponente neu gerendert wurde. Die Lösung ist die Verwendung eines Updaters. Ein Updater ermöglicht Ihnen den Zugriff auf den aktuellen Zustand und die sofortige Verwendung zur Aktualisierung anderer Elemente. So wird die Funktion changeCount() so aussehen.

changeCount = () => {
  this.setState((prevState) => {
    return { count: prevState.count - 1}
  })
}

Jetzt verlassen wir uns nicht mehr auf das Ergebnis von this.state. Die Zustände von count bauen aufeinander auf, sodass wir auf den korrekten Zustand zugreifen können, der sich mit jedem Aufruf von changeCount() ändert.

setState() sollte als asynchron behandelt werden — mit anderen Worten, erwarten Sie nicht immer, dass sich der Zustand nach dem Aufruf von setState() geändert hat.

Zusammenfassend

Wenn Sie mit setState() arbeiten, sind dies die wichtigsten Dinge, die Sie wissen sollten

  • Aktualisierungen des Komponentenzustands sollten mit setState() erfolgen
  • Sie können ein Objekt oder eine Funktion an setState() übergeben
  • Übergeben Sie eine Funktion, wenn möglich, um den Zustand mehrmals zu aktualisieren
  • Verlassen Sie sich nicht unmittelbar nach dem Aufruf von setState() auf this.state und verwenden Sie stattdessen die Updater-Funktion.