The Art of State: Zustandsmanagement in React-Anwendungen, Teil 1

Seite 4: immer für immutable Datenstrukturen

Inhaltsverzeichnis

Sowohl in Redux als auch in React müssen Reducer-Funktionen zwingend fortwährend neuen Zustand zurückgeben. Der alte Zustand, den sie reingereicht bekommen, darf nicht verändert werden. Deshalb wird der Zustand im Beispiel oben jedes Mal neu erzeugt und nicht verändert. Das Arbeiten mit solchen unveränderlichen (immutable) Datenstrukturen kann Code-lastig und fehleranfällig sein, selbst wenn Typ-Checker wie TypeScript hier einige Fehler frühzeitig erkennen.

Zur Illustration des Problems noch ein weiterer Reducer, der die Darstellung der Blog-Liste verwalten soll. Der darin verwaltete Zustand besteht aus einem Filter, der nur Posts mit einer gewissen "Like"-Anzahl anzeigt sowie die Informationen zur Sortierung (nach was wird sortiert und in welcher Richtung):

function blogListOptionsReducer(state, action) {
  switch (action.type) {
    // setzt den Filter neu; die Sortierreihenfolge
    // soll aber bestehen bleiben
    case "SET_BLOGLIST_FILTER_BY_LIKES": {
      return { ...state, likes: action.likes };
    }
    case "SET_BLOGLIST_SORT": {
      // setzt Sortierkriterium und -Reihenfolge neu,
      // der Likes-Filter soll aber bestehen bleiben
      return {
        ...state,
        sortBy: action.sortBy,
        order: action.direction,
      }; 
    }
    default:
      throw new Error("Invalid action!");
  }
}

Schon in diesem einfachen Beispiel ist darauf zu achten, den alten Zustand zu kopieren sowie die Kopie anzupassen und zurückzuliefern. Das Problem wird komplexer, je aufwendiger das Zustandsobjekt ist – ob es etwa aus Arrays und verschachtelten Strukturen besteht.

Eine Möglichkeit, den Code zu vereinfachen, ist die Bibliothek immer zu verwenden. Sie erlaubt es, mit Objekten zu arbeiten, als seien sie veränderlich. Tatsächlich arbeitet sie aber nicht auf dem Originalobjekt, sondern auf einem Objekt, das nur so aussieht wie das Originalobjekt. Auf ihm zeichnet immer alle ausgeführten Veränderungen transparent auf und erzeugt daraus ein komplett neues Objekt.

Die API ist dabei einfach: Die Anwendung übergibt der produce-Funktion ein Objekt sowie eine Callback-Funktion zum Verarbeiten des Objekts. immer ruft dann die übergebene Callback-Funktion mit einem "Draft-Objekt" auf. Auf ihm werden mit den regulären JavaScript-APIs Änderungen vorgenommen. Das Ausführen der Callback-Funktion erzeugt immer die Kopie des Originalobjekts, wendet darauf die aufgezeichneten Änderungen an und gibt die Kopie an den Aufrufer von produce zurück.

Die oben gezeigte Reducer-Funktion kann mit immer wie folgt aussehen:

import produce from "immer";

function blogListOptionsReducer(oldState, action) {
  return produce(oldState, state => {
    switch (action.type) {
      case "SET_BLOGLIST_FILTER_BY_LIKES": {
        // Sortierkriterium bleibt bestehen!
        state.likes = action.likes;
        break;
      }
      case "SET_BLOGLIST_SORT": {
        // Likes-filter bleibt bestehen!
        state.sortBy = action.sortBy;
        state.order = action.direction;
        break;
      }
      default: . . .
    }
  }
}

Der Zustand in React-Anwendung lässt sich in unterschiedliche Kategorien, zum Beispiel lokal und global oder einfach und komplex, einteilen. Das kann eine Hilfe bei der Wahl der geeigneten Technologie sein, mit der der jeweilige Zustand verwaltet und verarbeitet wird.

Dieser Artikel hat verschiedene Ansätze zur Arbeit mit einfachem und komplexem Zustand vorgestellt, die mit den React-eigenen Mitteln realisierbar sind. In weiteren Artikeln werden Verfahren und Bibliotheken zur Arbeit mit globalem Zustand vorgestellt.

Nils Hartmannist freiberuflicher Softwareentwickler, Trainer und Coach aus Hamburg. Er programmiert in Java und JavaScript und berät und schult Teams beim Ein- und Umstieg in die Entwicklung von Single-Page-Anwendungen mit den Schwerpunkten React, TypeScript und GraphQL. Nils ist Autor des Buches "React – Grundlagen, fortgeschrittene Techniken, Praxistipps" (dpunkt.verlag 2019).

(ane)