Den useReducer React Hook kennenlernen

Avatar of Kingsley Silas
Kingsley Silas am

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

useReducer ist einer von einer Handvoll React Hooks, die in React 16.7.0 ausgeliefert wurden. Er akzeptiert eine Reducer-Funktion mit dem initialen Zustand der Anwendung, gibt den aktuellen Zustand der Anwendung zurück und dispatcht dann eine Funktion.

Hier ist ein Beispiel, wie er verwendet wird;

const [state, dispatch] = useReducer(reducer, initialState);

Wofür ist das gut? Nun, denken Sie an jede Situation, in der es schön wäre, den ersten geladenen Zustand der Anwendung zu haben. Nehmen wir zum Beispiel den Ausgangspunkt einer interaktiven Karte. Vielleicht ist es eine App, die es dem Benutzer ermöglicht, ein benutzerdefiniertes Auto mit benutzerdefinierten Optionen aus einem Standardmodell zu erstellen. Hier ist eine ziemlich nette Demo einer Taschenrechner-App, die useReducer verwendet, um den Taschenrechner beim Leeren auf einen Standardzustand von Null zurückzusetzen.

Siehe den Pen
Basic React Hook Calculator
von Gianpierangelo De Palma (@dpgian)
auf CodePen.

Wir werden uns in diesem Beitrag noch ein paar weitere Beispiele ansehen, aber werfen wir zunächst einen Blick auf den Hook selbst, um eine bessere Vorstellung davon zu bekommen, was er ist und was genau er tut, wenn er verwendet wird.

Der allmächtige Reducer

Es ist schwierig, über useState zu sprechen, ohne auch die JavaScript-Methode reduce zu erwähnen. Wir haben sie ganz oben verlinkt, aber Sarahs Beitrag ist ein ausgezeichneter Überblick über Reducer und hilft, den Grundstein für das zu legen, was wir hier vorhaben.

Das Erste und Wichtigste, was man über einen Reducer verstehen muss, ist, dass er **immer nur einen Wert zurückgibt**. Die Aufgabe eines Reducers ist es zu reduzieren. Dieser eine Wert kann eine Zahl, ein String, ein Array oder ein Objekt sein, aber es wird immer nur einer sein. Reducer sind für viele Dinge wirklich großartig, aber sie sind besonders nützlich, um eine gewisse Logik auf eine Gruppe von Werten anzuwenden und mit einem einzigen Ergebnis zu enden.

Wenn wir also ein Array von Zahlen haben, wird reduce es auf eine einzige Zahl destillieren, die sich so oft addiert, wie es Werte gibt. Nehmen wir dieses einfache Array

const numbers = [1, 2, 3]

…und wir haben eine Funktion, die jedes Mal, wenn unser Reducer eine Berechnung durchführt, in die Konsole protokolliert. Dies wird uns helfen zu sehen, wie reduce das Array zu einer einzigen Zahl destilliert.

const reducer = function (tally, number) { 
	console.log(`Tally: ${tally}, Next number: ${number}, New Total: ${tally + number}`)
	return tally + number
}

Lassen Sie uns nun einen Reducer darauf anwenden. Wie wir bereits gesehen haben, führt reduce eine Funktion aus, die gegen einen Standardzustand ausgelöst wird. Stecken wir unsere reducer-Funktion und einen Anfangswert von Null hinein.

const total = numbers.reduce(reducer, 0)

Hier ist, was in die Konsole geloggt wird

"Tally: 0, Next number: 1, New Total: 1"
"Tally: 1, Next number: 2, New Total: 3"
"Tally: 3, Next number: 3, New Total: 6"

Sehen Sie, wie reduce einen Anfangswert nimmt und darauf aufbaut, während jede Zahl im Array zu ihm addiert wird, bis wir einen Endwert erhalten? In diesem Fall ist dieser Endwert 6.

Ich mag auch dieses (modifizierte) Beispiel von Dave Ceddia, das zeigt, wie reduce auf einem Array von Buchstaben verwendet werden kann, um ein Wort zu buchstabieren

var letters = ['r', 'e', 'd', 'u', 'c', 'e'];

// `reduce` takes 2 arguments:
//   - a function to do the reducing (you might say, a "reducer")
//   - an initial value for accumulatedResult
var word = letters.reduce(
	function(accumulatedResult, arrayItem) {
		return accumulatedResult + arrayItem;
	},
''); // <-- notice this empty string argument: it's the initial value

console.log(word) // => "reduce"

useReducer funktioniert mit Zuständen und Aktionen

Okay, das war viel Wiederholung, um zu dem zu kommen, worüber wir wirklich sprechen: useReducer. Es ist wichtig, das alles zu verstehen, denn Sie haben vielleicht schon bemerkt, wohin wir jetzt gehen, nachdem Sie gesehen haben, wie reduce eine Funktion gegen einen Anfangswert auslöst. Es ist die gleiche Art von Konzept, aber es gibt zwei Elemente als Array zurück: den aktuellen **Zustand** und eine **dispatch**-Funktion.

Mit anderen Worten

const [state, dispatch] = useReducer(reducer, initialArg, init);

Was ist mit diesem dritten init-Argument? Es ist ein optionaler Wert, der den Anfangszustand träge erstellt. Das bedeutet, dass wir den Anfangszustand/Wert mit einer init-Funktion außerhalb des Reducers berechnen können, anstatt einen expliziten Wert anzugeben. Das ist praktisch, wenn der Anfangswert unterschiedlich sein könnte, zum Beispiel basierend auf einem zuletzt gespeicherten Zustand anstelle eines konsistenten Wertes.

Um es zum Laufen zu bringen, müssen wir ein paar Dinge tun

  • Definieren Sie einen Anfangszustand.
  • Stellen Sie eine Funktion bereit, die Aktionen enthält, die den Zustand aktualisieren.
  • Lösen Sie useReducer aus, um einen aktualisierten Zustand zu versenden, der relativ zum Anfangszustand berechnet wird.

Das klassische Beispiel dafür ist eine Zähleranwendung. Tatsächlich verwenden die React-Dokumente diese, um das Konzept zu vermitteln. Hier wird das in die Praxis umgesetzt

Siehe den Pen
React useReducer 1
von Kingsley Silas Chijioke (@kinsomicrote)
auf CodePen.

Es ist ein gutes Beispiel, weil es zeigt, wie ein Anfangszustand (ein Nullwert) verwendet wird, um bei jeder Auslösung einer Aktion durch Klicken auf die Schaltfläche "Erhöhen" oder "Verringern" einen neuen Wert zu berechnen. Wir könnten sogar eine "Zurücksetzen"-Schaltfläche hinzufügen, um die Summe auf den Anfangszustand von Null zurückzusetzen.

Beispiel: Ein Autokonfigurator

Siehe den Pen
React useReducer – car example
von Geoff Graham (@geoffgraham)
auf CodePen.

In diesem Beispiel gehen wir davon aus, dass der Benutzer ein Auto zum Kauf ausgewählt hat. Wir möchten jedoch, dass die App es dem Benutzer ermöglicht, zusätzliche Optionen zum Auto hinzuzufügen. Jede Option hat einen Preis, der zum Basissumme hinzukommt.

Zuerst müssen wir den Anfangszustand erstellen, der aus dem Auto, einem leeren Array zur Verfolgung von Merkmalen und einem zusätzlichen Preis von 26.395 $ sowie einer Liste von Artikeln im Laden besteht, damit der Benutzer auswählen kann, was er möchte.

const initialState = {
  additionalPrice: 0,
  car: {
    price: 26395,
    name: "2019 Ford Mustang",
    image: "https://cdn.motor1.com/images/mgl/0AN2V/s1/2019-ford-mustang-bullitt.jpg",
    features: []
  },
  store: [
    { id: 1, name: "V-6 engine", price: 1500 },
    { id: 2, name: "Racing detail package", price: 1500 },
    { id: 3, name: "Premium sound system", price: 500 },
    { id: 4, name: "Rear spoiler", price: 250 }
  ]
};

Unsere Reducer-Funktion wird zwei Dinge behandeln: das Hinzufügen und Entfernen neuer Artikel.

const reducer = (state, action) => {
  switch (action.type) {
    case "REMOVE_ITEM":
      return {
        ...state,
        additionalPrice: state.additionalPrice - action.item.price,
        car: { ...state.car, features: state.car.features.filter((x) => x.id !== action.item.id)},
        store: [...state.store, action.item]
      };
    case "BUY_ITEM":
      return {
        ...state,
        additionalPrice: state.additionalPrice + action.item.price,
        car: { ...state.car, features: [...state.car.features, action.item] },
        store: state.store.filter((x) => x.id !== action.item.id)
      }
    default:
      return state;
  }
}

Wenn der Benutzer den gewünschten Artikel auswählt, aktualisieren wir die features für das Auto, erhöhen den additionalPrice und entfernen den Artikel auch aus dem Laden. Wir stellen sicher, dass die anderen Teile des Zustands unverändert bleiben.
Etwas Ähnliches tun wir, wenn ein Benutzer einen Artikel aus der Merkmaliste entfernt – wir reduzieren den zusätzlichen Preis und geben den Artikel wieder in den Laden zurück.
So sieht die App-Komponente aus.

const App = () => {
  const inputRef = useRef();
  const [state, dispatch] = useReducer(reducer, initialState);
  
  const removeFeature = (item) => {
    dispatch({ type: 'REMOVE_ITEM', item });
  }
  
  const buyItem = (item) => {
    dispatch({ type: 'BUY_ITEM', item })
  }
  
  return (
    <div>
      <div className="box">
        <figure className="image is-128x128">
          <img src={state.car.image} />
        </figure>
        <h2>{state.car.name}</h2>
        <p>Amount: ${state.car.price}</p>
        <div className="content">
          <h6>Extra items you bought:</h6>
          {state.car.features.length ? 
            (
              <ol type="1">
                {state.car.features.map((item) => (
                  <li key={item.id}>
                    <button
                      onClick={() => removeFeature(item)}
                      className="button">X
                    </button>
                    {item.name}
                  </li>
                ))}
              </ol>
            ) : <p>You can purchase items from the store.</p>
          }
        </div>
      </div>
      <div className="box">
        <div className="content">
          <h4>Store:</h4>
          {state.store.length ? 
            (
            <ol type="1">
              {state.store.map((item) => (
                <li key={item.id}>\
                  <button
                    onClick={() => buyItem(item)}
                    className="button">Buy
                  </button>
                  {item.name}
                </li>
              ))}
            </ol>
            ) : <p>No features</p>
          }
        </div>

        <div className="content">
        <h4>
          Total Amount: ${state.car.price + state.additionalPrice}
        </h4>
      </div>
      </div>
    </div>
  );
}

Die Aktionen, die versendet werden, enthalten die Details des ausgewählten Artikels. Wir verwenden den Aktionstyp, um zu bestimmen, wie die Reducer-Funktion die Aktualisierung des Zustands behandelt. Sie sehen, dass sich die gerenderte Ansicht basierend auf Ihren Aktionen ändert – der Kauf eines Artikels aus dem Laden entfernt den Artikel aus dem Laden und fügt ihn zur Merkmaliste hinzu. Auch die Gesamtsumme wird aktualisiert. Zweifellos gibt es einige Verbesserungen, die an der Anwendung vorgenommen werden könnten, dies dient nur Lernzwecken.

Was ist mit useState? Können wir das nicht stattdessen verwenden?

Ein scharfsinniger Leser hat sich das vielleicht schon die ganze Zeit gefragt. Ich meine, setState ist im Allgemeinen dasselbe, oder? Geben Sie einen zustandsbehafteten Wert und eine Funktion zurück, um eine Komponente mit diesem neuen Wert neu zu rendern.

const [state, setState] = useState(initialState);

Wir hätten auch den useState() Hook im Zählerbeispiel der React-Dokumente verwenden können. useReducer wird jedoch bevorzugt, wenn der Zustand komplizierte Übergänge durchlaufen muss. Kent C. Dodds hat eine Erklärung der Unterschiede zwischen den beiden verfasst und (obwohl er oft zu setState greift) liefert er einen guten Anwendungsfall für die Verwendung von useReducer stattdessen

Wenn ein Element Ihres Zustands vom Wert eines anderen Elements Ihres Zustands abhängt, ist es fast immer am besten, useReducer zu verwenden

Stellen Sie sich zum Beispiel vor, Sie schreiben ein Tic-Tac-Toe-Spiel. Sie haben ein Element namens squares, das nur ein Array aller Felder und ihres Wertes ist[. ]

Mein Faustregel ist, useReducer für komplexe Zustände zu verwenden, insbesondere wenn der Anfangszustand auf dem Zustand anderer Elemente basiert.

Oh warte, dafür haben wir doch schon Redux!

Wer von Ihnen mit Redux gearbeitet hat, weiß bereits alles, was wir hier behandelt haben, und das liegt daran, dass es entwickelt wurde, um die Context API zu nutzen, um gespeicherte Zustände zwischen Komponenten zu übergeben – ohne Zustandsdaten durch andere Komponenten leiten zu müssen, um dorthin zu gelangen.

Ersetzt useReducer also Redux? Nein. Ich meine, Sie können im Grunde Ihr eigenes Redux erstellen, indem Sie es mit dem useContext Hook verwenden, aber das bedeutet nicht, dass Redux nutzlos ist; Redux hat immer noch viele andere Funktionen und Vorteile, die es wert sind, in Betracht gezogen zu werden.

Wo haben Sie useReducer verwendet? Haben Sie eindeutige Fälle gefunden, in denen es besser ist als setState? Vielleicht können Sie mit den hier behandelten Dingen experimentieren, um etwas zu bauen. Hier sind ein paar Ideen

  • Ein Kalender, der sich auf das heutige Datum konzentriert, dem Benutzer aber erlaubt, andere Daten auszuwählen. Vielleicht sogar eine "Heute"-Schaltfläche hinzufügen, die den Benutzer zum heutigen Datum zurückbringt.
  • Sie können versuchen, das Autobeispiel zu verbessern – haben Sie eine Liste von Autos, die Benutzer kaufen können. Möglicherweise müssen Sie dies im Anfangszustand definieren, dann kann der Benutzer zusätzliche gewünschte Funktionen mit einer Gebühr hinzufügen. Diese Funktionen können vordefiniert oder vom Benutzer definiert werden.