Ein Reducer ist eine Funktion, die Änderungen am Zustand einer Anwendung bestimmt. Sie verwendet die empfangene Aktion, um diese Änderung zu bestimmen. Wir haben Tools wie Redux, die uns helfen, Zustandsänderungen einer Anwendung in einem einzigen Store zu verwalten, damit sie konsistent ablaufen.
Warum erwähnen wir Redux, wenn wir über Reducer sprechen? Redux stützt sich stark auf Reducer-Funktionen, die den vorherigen Zustand und eine Aktion aufnehmen, um den nächsten Zustand auszuführen.
Wir werden uns in diesem Beitrag ausschließlich auf Reducer konzentrieren. Unser Ziel ist es, uns mit der Reducer-Funktion vertraut zu machen, damit wir sehen können, wie sie zur Aktualisierung des Zustands einer Anwendung verwendet wird – und letztendlich die Rolle verstehen, die sie in einem Zustandsmanager wie Redux spielen.
Was wir unter „Zustand“ verstehen
Zustandsänderungen basieren auf der Interaktion eines Benutzers oder sogar auf etwas wie einer Netzwerkanfrage. Wenn der Zustand der Anwendung von Redux verwaltet wird, geschehen die Änderungen innerhalb einer Reducer-Funktion – dies ist der einzige Ort, an dem Zustandsänderungen stattfinden. Die Reducer-Funktion nutzt den Anfangszustand der Anwendung und etwas namens Aktion, um zu bestimmen, wie der neue Zustand aussehen wird.
Wenn wir in Matheklasse wären, könnten wir sagen
initial state + action = new state
In Bezug auf eine tatsächliche Reducer-Funktion sieht das so aus
const contactReducer = (state = initialState, action) => {
// Do something
}
Woher bekommen wir diesen Anfangszustand und die Aktion? Das sind Dinge, die wir definieren.
Der State-Parameter
Der state-Parameter, der an die Reducer-Funktion übergeben wird, muss der aktuelle Zustand der Anwendung sein. In diesem Fall nennen wir ihn unseren initialState, da er der erste (und aktuelle) Zustand sein wird und nichts ihm vorausgeht.
contactReducer(initialState, action)
Nehmen wir an, der Anfangszustand unserer App ist eine leere Kontaktliste und unsere Aktion ist das Hinzufügen eines neuen Kontakts zur Liste.
const initialState = {
contacts: []
}
Das erstellt unseren initialState, der dem state-Parameter entspricht, den wir für die Reducer-Funktion benötigen.
Der Action-Parameter
Eine action ist ein Objekt, das zwei Schlüssel und ihre Werte enthält. Die Zustandsaktualisierung, die im Reducer stattfindet, hängt immer vom Wert von action.type ab. In diesem Szenario demonstrieren wir, was passiert, wenn der Benutzer versucht, einen neuen Kontakt zu erstellen. Definieren wir also action.type als NEW_CONTACT.
const action = {
type: 'NEW_CONTACT',
name: 'John Doe',
location: 'Lagos Nigeria',
email: '[email protected]'
}
Normalerweise gibt es einen payload-Wert, der das enthält, was der Benutzer sendet und zur Aktualisierung des Zustands der Anwendung verwendet würde. Es ist wichtig zu beachten, dass action.type erforderlich ist, action.payload jedoch optional ist. Die Verwendung von payload bringt ein gewisses Maß an Struktur in das Aussehen des Aktions-Objekts.
Zustand aktualisieren
Der Zustand ist als immutable (unveränderlich) gedacht, was bedeutet, dass er nicht direkt geändert werden sollte. Um einen aktualisierten Zustand zu erstellen, können wir Object.assign verwenden oder den Spread-Operator nutzen.
Object.assign
const contactReducer = (state, action) => {
switch (action.type) {
case 'NEW_CONTACT':
return Object.assign({}, state, {
contacts: [
...state.contacts,
action.payload
]
})
default:
return state
}
}
Im obigen Beispiel haben wir Object.assign() verwendet, um sicherzustellen, dass wir den Zustand nicht direkt ändern. Stattdessen ermöglicht es uns, ein neues Objekt zurückzugeben, das mit dem an ihn übergebenen Zustand und dem vom Benutzer gesendeten Payload gefüllt ist.
Um Object.assign() verwenden zu können, ist es wichtig, dass das erste Argument ein leeres Objekt ist. Wenn Sie den Zustand als erstes Argument übergeben, wird er mutiert, was wir vermeiden wollen, um die Dinge konsistent zu halten.
Der Spread-Operator
Die Alternative zu object.assign() ist die Verwendung des Spread-Operators, wie folgt
const contactReducer = (state, action) => {
switch (action.type) {
case 'NEW_CONTACT':
return {
...state, contacts:
[...state.contacts, action.payload]
}
default:
return state
}
}
Dies stellt sicher, dass der eingehende Zustand intakt bleibt, während wir das neue Element unten anhängen.
Arbeiten mit einer Switch-Anweisung
Zuvor haben wir festgestellt, dass die Aktualisierung vom Wert von action.type abhängt. Die Switch-Anweisung bestimmt bedingt die Art der Aktualisierung, mit der wir es zu tun haben, basierend auf dem Wert von action.type.
Das bedeutet, dass ein typischer Reducer so aussehen wird
const addContact = (state, action) => {
switch (action.type) {
case 'NEW_CONTACT':
return {
...state, contacts:
[...state.contacts, action.payload]
}
case 'UPDATE_CONTACT':
return {
// Handle contact update
}
case 'DELETE_CONTACT':
return {
// Handle contact delete
}
case 'EMPTY_CONTACT_LIST':
return {
// Handle contact list
}
default:
return state
}
}
Es ist wichtig, dass wir default zurückgeben, für den Fall, dass der Wert von action.type, der im Aktions-Objekt angegeben ist, nicht mit dem übereinstimmt, was wir im Reducer haben – sagen wir, wenn die Aktion aus irgendeinem unbekannten Grund so aussieht
const action = {
type: 'UPDATE_USER_AGE',
payload: {
age: 19
}
}
Da wir diese Art von Aktionstyp nicht haben, wollen wir stattdessen zurückgeben, was wir im Zustand haben (den aktuellen Zustand der Anwendung). Alles, was das bedeutet, ist, dass wir uns im Moment nicht sicher sind, was der Benutzer zu erreichen versucht.
Putting everything together
Hier ist ein einfaches Beispiel dafür, wie ich die Reducer-Funktion in React implementiert habe.
Sehen Sie den Pen
reducer example von Kingsley Silas Chijioke (@kinsomicrote)
auf CodePen.
Sie können sehen, dass ich Redux nicht verwendet habe, aber dies ist sehr ähnlich zu der Art und Weise, wie Redux Reducer zur Speicherung und Aktualisierung von Zustandsänderungen verwendet. Die primäre Zustandsaktualisierung erfolgt in der Reducer-Funktion, und der zurückgegebene Wert setzt den aktualisierten Zustand der Anwendung.
Möchten Sie es ausprobieren? Sie können die Reducer-Funktion erweitern, damit der Benutzer das Alter eines Kontakts aktualisieren kann. Ich würde gerne sehen, was Sie im Kommentarbereich entwickeln!
Das Verständnis der Rolle, die Reducer in Redux spielen, sollte Ihnen ein besseres Verständnis dafür vermitteln, was im Hintergrund geschieht. Wenn Sie daran interessiert sind, mehr über die Verwendung von Reducern in Redux zu erfahren, lohnt es sich, die offizielle Dokumentation zu lesen.
Ich bin neugierig auf folgende Fragen
Wie organisiert man mehrere Reducer in einer großen App?
Sollte der gesamte Zustand in den Redux-Zustand gehen? Oder gibt es eine Möglichkeit, eine ausgewogene Mischung aus Redux- und lokalen Zuständen zu haben? Was sind die Dos und Donts?
Kennen Sie Beispiele für große Open-Source-Apps, die mit Redux geschrieben wurden?
Persönlich für mich (verwende Vue/Vuex, aber ähnliche Konzepte für den Zustand).
Alles, was als global angesehen werden kann (Benutzerkontoinformationen, Benachrichtigungen usw.), kommt in den Store.
Alles, was nicht „global“ ist, aber zwischen 2 oder mehr Komponenten geteilt wird, die keine direkten Eltern-Kind-Beziehungen haben, kommt ebenfalls in den Store.
Alles, was vollständig in einer einzigen Komponente enthalten ist, kommt in den lokalen Zustand.
Für alles, was außerhalb dieser Kategorien fällt, hilft die Fötalstellung und eine gute 30-minütige Weinsession.
Sie können mehrere Reducer erstellen, die nach logischen Abschnitten in Ihrer Anwendung unterteilt sind. Redux stellt eine combineReducers-Methode bereit, die genau das tut, was der Name impliziert.
Sie können eine größere React-Site mit der Browser-Erweiterung Redux Developer Tools besuchen, um zu sehen, wie andere Websites ihre Reducer aufteilen.
Außerdem bin ich ein großer Fan davon, die meisten Daten in Redux zu speichern, da sie als Cache verwendet werden können. Wenn Sie Daten lokal speichern, gehen sie bei cDU verloren. Sie können sie mit Localstorage cachen, aber mit Redux bleiben die Daten zwischen Routenänderungen erhalten.
Es kommt auf die persönliche Präferenz an. Ich arbeite in einer React-Agentur, und das ist eine Diskussion, die wir immer wieder führen.
Dies ist ein wirklich gut aufbereiteter Artikel. Mir gefiel, wie Sie die Reducer-Funktion isoliert und nicht speziell im Kontext von Reacts useReducer oder Redux zerlegt haben. Dieser Artikel schließt eine echte Lücke und ich werde ihn mit einigen meiner Teammitglieder teilen, die mit dem Reducer-Kontext kämpfen. Großer Kudos dafür.
Allerdings habe ich einen kleinen Einwand. Die Abhängigkeit von action.type ist eine Best Practice und eine gängige Konvention, könnte aber technisch durch eine beliebige Eigenschaft oder sogar eine Switch-Anweisung basierend auf einem zweiten Aktionsparameter ersetzt werden. Ich erwähne dies nur, weil eines der schwierigsten Dinge, mit denen ich beim Erlernen des Reducers zu kämpfen hatte, darin bestand, herauszufinden, was dogmatische Anforderungen für seine Funktionsweise waren und was gängige Konvention war.
Zusammen mit meinem kleinen Einwand präsentiere ich auch eine andere Möglichkeit, den Aktionstyp zu handhaben.
Durch die Verwendung eines Objekts anstelle einer Switch-Anweisung können einige der umständlicheren Aspekte von Switch (beliebige verschachtelte bedingte Logik, let und const in komplizierteren Fällen usw.) bewältigt werden.