v19.2Latest

Extrahieren von Zustandslogik in einen Reducer

Komponenten mit vielen Zustandsaktualisierungen, die über viele Event-Handler verteilt sind, können überwältigend werden. In solchen Fällen können Sie die gesamte Zustandsaktualisierungslogik außerhalb Ihrer Komponente in einer einzigen Funktion konsolidieren, die alsReducer bezeichnet wird.

Sie werden lernen
  • Was eine Reducer-Funktion ist
  • Wie manuseStatezuuseReducer
  • Wann man einen Reducer verwendet
  • Wie man einen gut schreibt

Zustandslogik mit einem Reducer konsolidieren

Wenn Ihre Komponenten an Komplexität zunehmen, kann es schwieriger werden, auf einen Blick alle verschiedenen Arten zu erkennen, wie der Zustand einer Komponente aktualisiert wird. Beispielsweise hält die KomponenteTaskAppunten ein Array vontasksim Zustand und verwendet drei verschiedene Event-Handler, um Aufgaben hinzuzufügen, zu entfernen und zu bearbeiten:

Jeder ihrer Event-Handler ruftsetTasksauf, um den Zustand zu aktualisieren. Wenn diese Komponente wächst, nimmt auch die Menge der darin verteilten Zustandslogik zu. Um diese Komplexität zu reduzieren und Ihre gesamte Logik an einem leicht zugänglichen Ort zu halten, können Sie diese Zustandslogik in eine einzelne Funktion außerhalb Ihrer Komponente verschieben,die als „Reducer“ bezeichnet wird.

Reducer sind eine andere Möglichkeit, den Zustand zu behandeln. Sie können in drei Schritten vonuseStatezuuseReducermigrieren:

  1. WechselnSie vom Setzen des Zustands zum Auslösen von Aktionen.
  2. SchreibenSie eine Reducer-Funktion.
  3. VerwendenSie den Reducer aus Ihrer Komponente.

Schritt 1: Vom Setzen des Zustands zum Auslösen von Aktionen wechseln

Ihre Event-Handler geben derzeitan, was zu tun ist, indem sie den Zustand setzen:

Entfernen Sie die gesamte Logik zum Setzen des Zustands. Was übrig bleibt, sind drei Event-Handler:

  • handleAddTask(text)wird aufgerufen, wenn der Benutzer „Hinzufügen“ drückt.
  • handleChangeTask(task)wird aufgerufen, wenn der Benutzer eine Aufgabe umschaltet oder „Speichern“ drückt.
  • handleDeleteTask(taskId)wird aufgerufen, wenn der Benutzer „Löschen“ drückt.

Die Verwaltung des Zustands mit Reducern unterscheidet sich leicht vom direkten Setzen des Zustands. Anstatt React durch Setzen des Zustands „mitzuteilen, was zu tun ist“, geben Sie durch das Auslösen von „Aktionen“ aus Ihren Event-Handlern an, „was der Benutzer gerade getan hat“. (Die Logik zur Zustandsaktualisierung wird woanders leben!) Anstatt also „tasks“ über einen Event-Handler zu „setzen“, lösen Sie eine Aktion „Aufgabe hinzugefügt/geändert/gelöscht“ aus. Dies beschreibt die Absicht des Benutzers besser.

Das Objekt, das Sie andispatchübergeben, wird als „Aktion“ bezeichnet:

Es handelt sich um ein reguläres JavaScript-Objekt. Sie entscheiden, was Sie hineinlegen, aber im Allgemeinen sollte es die minimalen Informationen darüber enthalten,was passiert ist. (Diedispatch-Funktion selbst fügen Sie in einem späteren Schritt hinzu.)

Hinweis

Ein Aktionsobjekt kann jede Form haben.

Konventionell ist es üblich, ihm einen Stringtypezu geben, der beschreibt, was passiert ist, und zusätzliche Informationen in anderen Feldern zu übergeben. Dertypeist spezifisch für eine Komponente, daher wäre in diesem Beispiel entweder'added'oder'added_task'in Ordnung. Wählen Sie einen Namen, der beschreibt, was passiert ist!

Schritt 2: Eine Reducer-Funktion schreiben

Eine Reducer-Funktion ist der Ort, an dem Sie Ihre Zustandslogik unterbringen. Sie nimmt zwei Argumente entgegen: den aktuellen Zustand und das Aktionsobjekt, und gibt den nächsten Zustand zurück:

React setzt den Zustand auf das, was du aus dem Reducer zurückgibst.

Um deine Zustandsänderungslogik von deinen Event-Handlern in eine Reducer-Funktion zu verschieben, wirst du in diesem Beispiel:

  1. Den aktuellen Zustand (tasks) als ersten Parameter deklarieren.
  2. Das action-Objekt als zweiten Parameter deklarieren.
  3. Den nächstenZustand aus dem Reducer zurückgeben (den React dann als Zustand setzt).

Hier ist die gesamte Zustandsänderungslogik in eine Reducer-Funktion migriert:

Da die Reducer-Funktion den Zustand (tasks) als Parameter nimmt, kannst du sieaußerhalb deiner Komponente deklarieren.Das verringert die Einrückungsebene und kann deinen Code leichter lesbar machen.

Hinweis

Der obige Code verwendet if/else-Anweisungen, aber es ist üblich, in Reducernswitch-Anweisungenzu verwenden. Das Ergebnis ist dasselbe, aber switch-Anweisungen sind oft auf einen Blick leichter zu lesen.

Wir werden sie im Rest dieser Dokumentation wie folgt verwenden:

Wir empfehlen, jedencase-Block in geschweifte Klammern{und}einzuschließen, damit Variablen, die in verschiedenencases deklariert werden, sich nicht gegenseitig beeinflussen. Außerdem sollte eincasenormalerweise mit einemreturnenden. Wenn du vergisst,returnzu schreiben, wird der Code zum nächstencase„durchfallen“, was zu Fehlern führen kann!

Wenn du mit switch-Anweisungen noch nicht vertraut bist, ist die Verwendung von if/else völlig in Ordnung.

Schritt 3: Verwende den Reducer in deiner Komponente

Schließlich musst du dentasksReducermit deiner Komponente verbinden. Importiere denuseReducer-Hook von React:

Dann kannst duuseStateersetzen:

durchuseReducerwie folgt:

DeruseReducer-Hook ähneltuseState– du musst ihm einen Anfangszustand übergeben und er gibt einen zustandsbehafteten Wert und eine Möglichkeit zurück, den Zustand zu setzen (in diesem Fall die Dispatch-Funktion). Aber es gibt einen kleinen Unterschied.

DeruseReducer-Hook nimmt zwei Argumente:

  1. Eine Reducer-Funktion
  2. Einen Anfangszustand

Und er gibt zurück:

  1. Einen zustandsbehafteten Wert
  2. Eine Dispatch-Funktion (um Benutzeraktionen an den Reducer zu „dispatchen“)

Jetzt ist alles vollständig verbunden! Hier ist der Reducer am Ende der Komponentendatei deklariert:

Wenn Sie möchten, können Sie den Reducer sogar in eine andere Datei verschieben:

Die Komponentenlogik kann besser lesbar sein, wenn Sie die Zuständigkeiten auf diese Weise trennen. Jetzt legen die Event-Handler nur fest,was passiert ist, indem sie Aktionen dispatch-en, und die Reducer-Funktion bestimmt,wie der State aktualisiert wird, als Reaktion darauf.

Vergleich vonuseStateunduseReducer

Reducer haben auch Nachteile! Hier sind einige Möglichkeiten, sie zu vergleichen:

  • Codegröße:Im Allgemeinen musst du mituseStateweniger Code von Anfang an schreiben. MituseReducermusst du sowohl eine Reducer-Funktionals auchDispatch-Aktionen schreiben. Allerdings kannuseReducerhelfen, Code einzusparen, wenn viele Event-Handler den State auf ähnliche Weise ändern.
  • Lesbarkeit:useStateist sehr einfach zu lesen, wenn die State-Updates einfach sind. Wenn sie komplexer werden, können sie den Code deiner Komponente aufblähen und das Überfliegen erschweren. In diesem Fall ermöglichtuseReducereine saubere Trennung derArt und Weiseder Update-Logik von demWas passiert istder Event-Handler.
  • Debugging:Wenn du einen Bug mituseStatehast, kann es schwierig sein zu erkennen,woder State falsch gesetzt wurde undwarum. MituseReducerkannst du einen Console-Log in deinen Reducer einfügen, um jedes State-Update zu sehen undwarumes passiert ist (aufgrund welcheraction). Wenn jedeactionkorrekt ist, weißt du, dass der Fehler in der Reducer-Logik selbst liegt. Allerdings musst du mehr Code durchgehen als mituseState.
  • Testing:Ein Reducer ist eine reine Funktion, die nicht von deiner Komponente abhängt. Das bedeutet, dass du ihn separat und isoliert exportieren und testen kannst. Obwohl es generell am besten ist, Komponenten in einer realistischeren Umgebung zu testen, kann es bei komplexer State-Update-Logik nützlich sein, zu überprüfen, dass dein Reducer für einen bestimmten Anfangszustand und eine bestimmte Aktion einen bestimmten State zurückgibt.
  • Persönliche Präferenz:Manche Leute mögen Reducer, andere nicht. Das ist in Ordnung. Es ist eine Frage der Präferenz. Du kannst immer zwischenuseStateunduseReducerhin und her wechseln: sie sind gleichwertig!

Wir empfehlen die Verwendung eines Reducers, wenn du häufig auf Bugs aufgrund falscher State-Updates in einer Komponente stößt und mehr Struktur in ihren Code bringen möchtest. Du musst nicht für alles Reducer verwenden: Du kannst sie frei kombinieren! Du kannst sogaruseStateunduseReducerin derselben Komponente verwenden.

Reduzierer gut schreiben

Behalte diese beiden Tipps im Hinterkopf, wenn du Reduzierer schreibst:

  • Reducer müssen rein sein.Ähnlich wieState-Aktualisierungsfunktionen, werden Reducer während des Renderns ausgeführt! (Aktionen werden bis zum nächsten Render in eine Warteschlange gestellt.) Das bedeutet, dass Reducerrein sein müssen– dieselben Eingaben führen immer zum selben Ergebnis. Sie sollten keine Anfragen senden, Timeouts planen oder Nebeneffekte ausführen (Operationen, die Dinge außerhalb der Komponente beeinflussen). Sie solltenObjekteundArraysohne Mutationen aktualisieren.
  • Jede Aktion beschreibt eine einzelne Benutzerinteraktion, selbst wenn diese zu mehreren Änderungen in den Daten führt.Wenn ein Benutzer beispielsweise in einem Formular mit fünf Feldern, die von einem Reducer verwaltet werden, auf „Zurücksetzen“ drückt, ist es sinnvoller, einereset_form-Aktion zu dispatchen, als fünf separateset_field-Aktionen. Wenn Sie jede Aktion in einem Reducer protokollieren, sollte dieses Protokoll klar genug sein, um nachzuvollziehen, welche Interaktionen oder Antworten in welcher Reihenfolge stattgefunden haben. Das hilft beim Debugging!

Kompakte Reducer mit Immer schreiben

Genau wie beimAktualisieren von ObjektenundArraysim regulären State können Sie die Immer-Bibliothek verwenden, um Reducer kompakter zu gestalten. Hier erlaubt IhnenuseImmerReducer, den State mitpushoder Zuweisungen wiearr[i] =zu mutieren:

Reducer müssen rein sein, daher sollten sie den State nicht mutieren. Aber Immer stellt Ihnen ein speziellesdraft-Objekt zur Verfügung, das sicher mutiert werden kann. Im Hintergrund erstellt Immer eine Kopie Ihres States mit den Änderungen, die Sie amdraftvorgenommen haben. Deshalb können vonuseImmerReducerverwaltete Reducer ihr erstes Argument mutieren und müssen keinen State zurückgeben.

Zusammenfassung

  • Um vonuseStatezuuseReducerzu wechseln:
    1. Dispatchen Sie Aktionen aus Event-Handlern.
    2. Schreiben Sie eine Reducer-Funktion, die den nächsten State für einen gegebenen State und eine Aktion zurückgibt.
    3. Ersetzen SieuseStatedurchuseReducer.
  • Reducer erfordern etwas mehr Code, helfen aber beim Debugging und Testen.
  • Reducer müssen rein sein.
  • Jede Aktion beschreibt eine einzelne Benutzerinteraktion.
  • Verwenden Sie Immer, wenn Sie Reducer in einem mutierenden Stil schreiben möchten.

Try out some challenges

Challenge 1 of 4:Dispatch actions from event handlers #

Currently, the event handlers in ContactList.js and Chat.js have // TODO comments. This is why typing into the input doesn’t work, and clicking on the buttons doesn’t change the selected recipient.

Replace these two // TODOs with the code to dispatch the corresponding actions. To see the expected shape and the type of the actions, check the reducer in messengerReducer.js. The reducer is already written so you won’t need to change it. You only need to dispatch the actions in ContactList.js and Chat.js.