Dieses Tutorial ist der letzte Teil einer dreiteiligen Serie von Brad Westfall. Wir werden lernen, wie man den Zustand (state) über eine gesamte Anwendung hinweg effizient und auf eine Weise verwaltet, die ohne gefährliche Komplexität skaliert werden kann. Wir sind auf unserer React-Reise schon so weit gekommen, dass es sich lohnt, hier die Ziellinie zu überqueren und das volle Potenzial dieses Entwicklungsansatzes auszuschöpfen.
Artikelserie
- React Router
- Container-Komponenten
- Redux (Sie sind hier!)
Redux ist ein Tool zur Verwaltung sowohl des Datenzustands als auch des UI-Zustands in JavaScript-Anwendungen. Es ist ideal für Single Page Applications (SPAs), bei denen die Verwaltung des Zustands über die Zeit komplex werden kann. Es ist auch framework-agnostisch, obwohl es mit Blick auf React geschrieben wurde, kann es sogar mit Angular oder einer jQuery-Anwendung verwendet werden.
Außerdem entstand es aus einem Experiment mit „Zeitreisen“ — Tatsache, dazu kommen wir später!
Wie in unserem vorherigen Tutorial gezeigt, lässt React Daten durch Komponenten „fließen“. Genauer gesagt spricht man von „unidirektionalem Datenfluss“ — Daten fließen in eine Richtung von Eltern zu Kindern. Bei dieser Eigenschaft ist nicht offensichtlich, wie zwei Komponenten ohne Eltern-Kind-Beziehung in React kommunizieren würden.
React empfiehlt keine direkte Kommunikation zwischen Komponenten auf diese Weise. Selbst wenn es Funktionen zur Unterstützung dieses Ansatzes hätte, wird dies von vielen als schlechte Praxis angesehen, da die direkte Kommunikation zwischen Komponenten fehleranfällig ist und zu Spaghetti-Code führt — ein alter Begriff für Code, der schwer zu verfolgen ist.
React bietet einen Vorschlag, erwartet aber, dass Sie ihn selbst implementieren. Hier ist ein Abschnitt aus den React-Dokumenten
Für die Kommunikation zwischen zwei Komponenten, die keine Eltern-Kind-Beziehung haben, können Sie Ihr eigenes globales Ereignissystem einrichten. … Das Flux-Muster ist eine der möglichen Möglichkeiten, dies zu organisieren.
Hier kommt Redux ins Spiel. Redux bietet eine Lösung, um den gesamten Anwendungszustand an einem Ort zu speichern, der als „Store“ bezeichnet wird. Komponenten „dispatchen“ dann Zustandsänderungen an den Store, nicht direkt an andere Komponenten. Die Komponenten, die über Zustandsänderungen informiert werden müssen, können den Store „abonnieren“
Der Store kann als „Mittelsmann“ für alle Zustandsänderungen in der Anwendung betrachtet werden. Wenn Redux beteiligt ist, kommunizieren Komponenten nicht direkt miteinander, sondern alle Zustandsänderungen müssen über die einzige Quelle der Wahrheit, den Store, laufen.
Das unterscheidet sich stark von anderen Strategien, bei denen Teile der Anwendung direkt miteinander kommunizieren. Manchmal wird argumentiert, dass diese Strategien fehleranfällig und verwirrend in der Argumentation sind.
Mit Redux ist klar, dass alle Komponenten ihren Zustand aus dem Store beziehen. Es ist auch klar, wohin Komponenten ihre Zustandsänderungen senden sollen – ebenfalls an den Store. Die Komponente, die die Änderung initiiert, kümmert sich nur darum, die Änderung an den Store zu dispatchen, und muss sich nicht um eine Liste anderer Komponenten kümmern, die die Zustandsänderung benötigen. Auf diese Weise erleichtert Redux die Argumentation über den Datenfluss.
Das allgemeine Konzept der Verwendung von Store(s) zur Koordination des Anwendungszustands ist ein Muster, das als Flux-Muster bekannt ist. Es ist ein Designmuster, das unidirektionale Datenflussarchitekturen wie React ergänzt. Redux ähnelt Flux, aber wie nah sind sie sich?
Redux ist „Flux-ähnlich“
Flux ist ein Muster, kein Tool wie Redux, man kann es also nicht herunterladen. Redux ist jedoch ein Tool, das vom Flux-Muster und anderen Dingen wie Elm inspiriert wurde. Es gibt viele Anleitungen, die Redux mit Flux vergleichen. Die meisten kommen zu dem Schluss, dass Redux Flux ist oder Flux-ähnlich ist, je nachdem, wie streng man die Regeln von Flux definiert. Letztendlich spielt es keine Rolle. Facebook mag und unterstützt Redux so sehr, dass sie seinen Hauptentwickler, Dan Abramov, eingestellt haben.
Dieser Artikel geht davon aus, dass Sie mit dem Flux-Muster überhaupt nicht vertraut sind. Aber wenn Sie es sind, werden Sie einige kleine Unterschiede bemerken, insbesondere in Anbetracht der drei Leitprinzipien von Redux
1. Single Source of Truth
Redux verwendet nur einen Store für den gesamten Anwendungszustand. Da sich der gesamte Zustand an einem Ort befindet, nennt Redux dies die single source of truth (einzige Quelle der Wahrheit).
Die Datenstruktur des Stores liegt letztendlich bei Ihnen, aber für eine reale Anwendung ist es typischerweise ein tief verschachteltes Objekt.
Dieser Ansatz von Redux, nur einen Store zu verwenden, ist einer der Hauptunterschiede zum Ansatz von Flux mit mehreren Stores.
2. Zustand ist schreibgeschützt
Laut den Redux-Dokumenten gilt: „Die einzige Möglichkeit, den Zustand zu verändern, besteht darin, eine Aktion auszugeben, ein Objekt, das beschreibt, was passiert ist.“
Das bedeutet, dass die Anwendung den Zustand nicht direkt ändern kann. Stattdessen werden „Aktionen“ dispatched, um die Absicht auszudrücken, den Zustand im Store zu ändern.
Das Store-Objekt selbst hat eine sehr kleine API mit nur vier Methoden
store.dispatch(action)store.subscribe(listener)store.getState()replaceReducer(nextReducer)
Wie Sie sehen, gibt es keine Methode zum Setzen des Zustands. Das Dispatchen einer Aktion ist daher die einzige Möglichkeit für den Anwendungscode, eine Zustandsänderung auszudrücken.
var action = {
type: 'ADD_USER',
user: {name: 'Dan'}
};
// Assuming a store object has been created already
store.dispatch(action);
Die Methode dispatch() sendet ein Objekt an Redux, das als Aktion bekannt ist. Die Aktion kann als „Payload“ beschrieben werden, die einen type und alle anderen Daten enthält, die zur Aktualisierung des Zustands verwendet werden könnten – in diesem Fall ein Benutzer. Beachten Sie, dass die Gestaltung eines Aktionsobjekts nach der type-Eigenschaft Ihnen überlassen ist.
3. Änderungen werden mit reinen Funktionen vorgenommen
Wie gerade beschrieben, erlaubt Redux der Anwendung nicht, direkte Änderungen am Zustand vorzunehmen. Stattdessen „beschreibt“ die dispatched Aktion die Zustandsänderung und die Absicht, den Zustand zu ändern. Reducer sind Funktionen, die Sie schreiben, die dispatched Aktionen verarbeiten und den Zustand tatsächlich ändern können.
Ein Reducer nimmt den aktuellen Zustand als Argument entgegen und kann den Zustand nur durch Rückgabe eines neuen Zustands ändern.
// Reducer Function
var someReducer = function(state, action) {
...
return state;
}
Reducer sollten als „reine“ Funktionen geschrieben werden, ein Begriff, der eine Funktion mit den folgenden Eigenschaften beschreibt
- Sie führt keine externen Netzwerk- oder Datenbankaufrufe durch.
- Ihr Rückgabewert hängt ausschließlich von den Werten ihrer Parameter ab.
- Ihre Argumente sollten als „unveränderlich“ (immutable) betrachtet werden, was bedeutet, dass sie nicht geändert werden sollten.
- Das Aufrufen einer reinen Funktion mit denselben Argumenten gibt immer denselben Wert zurück.
Diese werden als „rein“ bezeichnet, weil sie nichts anderes tun, als einen Wert basierend auf ihren Parametern zurückzugeben. Sie haben keine Nebenwirkungen in andere Teile des Systems.
Unser erster Redux Store
Erstellen Sie zunächst einen Store mit Redux.createStore() und übergeben Sie alle Reducer als Argumente. Betrachten wir ein kleines Beispiel mit nur einem Reducer
// Note that using .push() in this way isn't the
// best approach. It's just the easiest to show
// for this example. We'll explain why in the next section.
// The Reducer Function
var userReducer = function(state, action) {
if (state === undefined) {
state = [];
}
if (action.type === 'ADD_USER') {
state.push(action.user);
}
return state;
}
// Create a store by passing in the reducer
var store = Redux.createStore(userReducer);
// Dispatch our first action to express an intent to change the state
store.dispatch({
type: 'ADD_USER',
user: {name: 'Dan'}
});
Hier ist eine kurze Zusammenfassung dessen, was passiert:
- Der Store wird mit einem Reducer erstellt.
- Der Reducer legt fest, dass der anfängliche Zustand der Anwendung ein leeres Array ist. *
- Es wird ein Dispatch mit einem neuen Benutzer in der Aktion selbst durchgeführt.
- Der Reducer fügt den neuen Benutzer zum Zustand hinzu und gibt ihn zurück, wodurch der Store aktualisiert wird.
* Der Reducer wird in dem Beispiel tatsächlich zweimal aufgerufen — einmal, wenn der Store erstellt wird, und dann noch einmal nach dem Dispatch.
Wenn der Store erstellt wird, ruft Redux sofort die Reducer auf und verwendet deren Rückgabewerte als anfänglichen Zustand. Dieser erste Aufruf des Reducers sendet undefined für den Zustand. Der Reducer-Code antizipiert dies und gibt ein leeres Array zurück, um den anfänglichen Zustand des Stores zu starten.
Reducer werden auch jedes Mal aufgerufen, wenn Aktionen dispatched werden. Da der vom Reducer zurückgegebene Zustand unser neuer Zustand im Store wird, erwartet Redux immer, dass Reducer einen Zustand zurückgeben.
Im Beispiel erfolgt der zweite Aufruf unseres Reducers nach dem Dispatch. Denken Sie daran, eine dispatched Aktion beschreibt die Absicht, den Zustand zu ändern, und enthält oft die Daten für den neuen Zustand. Dieses Mal übergibt Redux den aktuellen Zustand (immer noch ein leeres Array) zusammen mit dem Aktionsobjekt an den Reducer. Das Aktionsobjekt, jetzt mit einer Type-Eigenschaft von 'ADD_USER', ermöglicht es dem Reducer, zu wissen, wie der Zustand geändert werden soll.
Man kann sich Reducer leicht als Trichter vorstellen, die es dem Zustand ermöglichen, sie zu durchlaufen. Das liegt daran, dass Reducer immer Zustand empfangen und zurückgeben, um den Store zu aktualisieren.
Basierend auf dem Beispiel ist unser Store nun ein Array mit einem Benutzerobjekt.
store.getState(); // => [{name: 'Dan'}]
Zustand nicht mutieren, kopieren Sie ihn
Obwohl der Reducer in unserem Beispiel technisch funktioniert, mutiert er den Zustand, was eine schlechte Praxis ist. Obwohl Reducer für die Zustandsänderung verantwortlich sind, sollten sie niemals das Argument „current state“ direkt mutieren. Deshalb sollten wir .push(), eine Mutationsmethode, nicht für das State-Argument des Reducers verwenden.
Argumente, die an den Reducer übergeben werden, sollten als unveränderlich betrachtet werden. Mit anderen Worten, sie sollten nicht direkt geändert werden. Anstelle einer direkten Mutation können wir nicht-mutierende Methoden wie .concat() verwenden, um im Grunde eine Kopie des Arrays zu erstellen, und dann werden wir die Kopie ändern und zurückgeben.
var userReducer = function(state = [], action) {
if (action.type === 'ADD_USER') {
var newState = state.concat([action.user]);
return newState;
}
return state;
}
Mit dieser Aktualisierung des Reducers führt das Hinzufügen eines neuen Benutzers dazu, dass eine Kopie des State-Arguments geändert und zurückgegeben wird. Wenn kein neuer Benutzer hinzugefügt wird, wird der ursprüngliche Zustand zurückgegeben, anstatt eine Kopie zu erstellen.
Es gibt unten einen ganzen Abschnitt über unveränderliche Datenstrukturen, der mehr Licht auf diese Art von Best Practices wirft.
Mehrere Reducer
Das letzte Beispiel war eine schöne Einführung, aber die meisten Anwendungen benötigen einen komplexeren Zustand für die gesamte Anwendung. Da Redux nur einen Store verwendet, müssen wir verschachtelte Objekte verwenden, um den Zustand in verschiedene Abschnitte zu organisieren. Stellen wir uns vor, wir möchten, dass unser Store diesem Objekt ähnelt:
{
userState: { ... },
widgetState: { ... }
}
Es ist immer noch „ein Store = ein Objekt“ für die gesamte Anwendung, aber es hat verschachtelte Objekte für userState und widgetState, die alle Arten von Daten enthalten können. Das mag übermäßig simpel erscheinen, aber es ist tatsächlich nicht weit entfernt von einem echten Redux Store.
Um einen Store mit verschachtelten Objekten zu erstellen, müssen wir jeden Abschnitt mit einem Reducer definieren.
import { createStore, combineReducers } from 'redux';
// The User Reducer
const userReducer = function(state = {}, action) {
return state;
}
// The Widget Reducer
const widgetReducer = function(state = {}, action) {
return state;
}
// Combine Reducers
const reducers = combineReducers({
userState: userReducer,
widgetState: widgetReducer
});
const store = createStore(reducers);
Die Verwendung von combineReducers() ermöglicht es uns, unseren Store in Bezug auf verschiedene logische Abschnitte zu beschreiben und Reducer jedem Abschnitt zuzuordnen. Wenn nun jeder Reducer den anfänglichen Zustand zurückgibt, gelangt dieser Zustand in den jeweiligen userState- oder widgetState-Abschnitt des Stores.
Es ist sehr wichtig zu beachten, dass jetzt jedem Reducer sein jeweiliger Unterabschnitt des gesamten Zustands übergeben wird, nicht der gesamte Zustand des Stores wie bei dem Beispiel mit einem Reducer. Dann gilt der von jedem Reducer zurückgegebene Zustand für seinen Unterabschnitt.
Welcher Reducer wird nach einem Dispatch aufgerufen?
Alle. Der Vergleich von Reducern mit Trichtern wird noch deutlicher, wenn wir bedenken, dass jedes Mal, wenn eine Aktion dispatched wird, alle Reducer aufgerufen werden und die Möglichkeit haben, ihren jeweiligen Zustand zu aktualisieren.
Ich sage „ihren“ Zustand mit Bedacht, weil das Argument „current state“ des Reducers und sein zurückgegebener „updated“ state nur den Abschnitt des Stores betreffen, der diesem Reducer zugeordnet ist. Denken Sie daran, wie im vorherigen Abschnitt erwähnt, dass jedem Reducer nur sein jeweiliger Zustand übergeben wird, nicht der gesamte Zustand.
Aktionsstrategien
Es gibt tatsächlich einige Strategien zum Erstellen und Verwalten von Aktionen und Aktionstypen. Obwohl sie sehr nützlich sind, sind sie nicht so kritisch wie einige andere Informationen in diesem Artikel. Um den Artikel kleiner zu halten, haben wir die grundlegenden Aktionsstrategien, die Sie kennen sollten, im GitHub-Repository dokumentiert, das zu dieser Serie gehört.
Unveränderliche Datenstrukturen
„Die Form des Zustands liegt bei Ihnen: Es kann ein Primitiv, ein Array, ein Objekt oder sogar eine Immutable.js-Datenstruktur sein. Der einzige wichtige Teil ist, dass Sie das Zustandsobjekt nicht mutieren sollten, sondern ein neues Objekt zurückgeben, wenn sich der Zustand ändert.“ – Redux-Dokumente
Diese Aussage sagt viel aus, und wir haben in diesem Tutorial bereits auf diesen Punkt angespielt. Wenn wir anfangen würden, die Einzelheiten sowie Vor- und Nachteile dessen zu diskutieren, was es bedeutet, unveränderlich vs. veränderlich zu sein, könnten wir einen ganzen Blogartikel damit füllen. Daher werde ich nur einige Hauptpunkte hervorheben.
Zunächst
- Die primitiven Datentypen von JavaScript (Number, String, Boolean, Undefined und Null) sind bereits unveränderlich.
- Objekte, Arrays und Funktionen sind veränderlich.
Es wurde gesagt, dass die Veränderbarkeit von Datenstrukturen anfällig für Bugs ist. Da unser Store aus Zustandsobjekten und Arrays bestehen wird, müssen wir eine Strategie implementieren, um den Zustand unveränderlich zu halten.
Stellen wir uns ein state-Objekt vor, bei dem wir eine Eigenschaft ändern müssen. Hier sind drei Möglichkeiten:
// Example One
state.foo = '123';
// Example Two
Object.assign(state, { foo: 123 });
// Example Three
var newState = Object.assign({}, state, { foo: 123 });
Das erste und zweite Beispiel mutieren das State-Objekt. Das zweite Beispiel mutiert, weil Object.assign() alle seine Argumente in das erste Argument zusammenführt. Aber dieser Grund ist auch, warum das dritte Beispiel den Zustand nicht mutiert.
Das dritte Beispiel führt den Inhalt von state und {foo: 123} in ein völlig neues leeres Objekt zusammen. Dies ist ein gängiger Trick, der es uns ermöglicht, im Grunde eine Kopie des Zustands zu erstellen und die Kopie zu mutieren, ohne den ursprünglichen state zu beeinflussen.
Der Objekt-Spread-Operator ist eine weitere Möglichkeit, den Zustand unveränderlich zu halten.
const newState = { ...state, foo: 123 };
Eine sehr detaillierte Erklärung, was passiert und warum dies für Redux nützlich ist, finden Sie in den Dokumenten zu diesem Thema.
Object.assign() und Spread-Operatoren sind beide ES2015.
Zusammenfassend lässt sich sagen, dass es viele Möglichkeiten gibt, Objekte und Arrays explizit unveränderlich zu halten. Viele Entwickler verwenden Bibliotheken wie seamless-immutable, Mori oder sogar Facebooks eigenes Immutable.js.
Anfangszustand und Zeitreisen
Wenn Sie die Dokumente lesen, bemerken Sie möglicherweise ein zweites Argument für createStore(), das für den „Anfangszustand“ vorgesehen ist. Dies mag wie eine Alternative zu Reducern erscheinen, die den anfänglichen Zustand erstellen. Dieser anfängliche Zustand sollte jedoch nur für die „Zustandshydratation“ verwendet werden.
Stellen Sie sich vor, ein Benutzer führt eine Aktualisierung Ihrer SPA durch und der Zustand des Stores wird auf die anfänglichen Zustände des Reducers zurückgesetzt. Dies ist möglicherweise nicht erwünscht.
Stellen Sie sich stattdessen vor, Sie hätten eine Strategie verwendet, um den Store beizubehalten, und Sie können ihn dann bei der Aktualisierung wieder in Redux hydratisieren. Dies ist der Grund, warum der anfängliche Zustand an createStore() gesendet wird.
Dies wirft jedoch ein interessantes Konzept auf. Wenn es so billig und einfach ist, alten Zustand wiederherzustellen, könnte man sich das Äquivalent einer „Zeitreise“ im Zustand in seiner App vorstellen. Dies kann nützlich für das Debuggen oder sogar für Rückgängig-/Wiederherstellen-Funktionen sein. All Ihren Zustand in einem Store zu haben, ist aus diesen und vielen Gründen sehr sinnvoll! Dies ist nur ein Grund, warum uns unveränderlicher Zustand hilft.
In einem Interview wurde Dan Abramov gefragt „Warum hast du Redux entwickelt?“
Ich wollte kein Flux-Framework erstellen. Als React Europe zum ersten Mal angekündigt wurde, schlug ich einen Vortrag über „Hot Reloading und Zeitreisen“ vor, aber um ehrlich zu sein, hatte ich keine Ahnung, wie man Zeitreisen implementiert.
Redux mit React
Wie bereits erwähnt, ist Redux framework-agnostisch. Das Verständnis der Kernkonzepte von Redux ist wichtig, bevor Sie überhaupt darüber nachdenken, wie es mit React funktioniert. Aber jetzt sind wir bereit, eine Container-Komponente aus dem letzten Artikel zu nehmen und Redux darauf anzuwenden.
Zuerst die Originalkomponente ohne Redux
import React from 'react';
import axios from 'axios';
import UserList from '../views/list-user';
const UserListContainer = React.createClass({
getInitialState: function() {
return {
users: []
};
},
componentDidMount: function() {
axios.get('/path/to/user-api').then(response => {
this.setState({users: response.data});
});
},
render: function() {
return <UserList users={this.state.users} />;
}
});
export default UserListContainer;
Sicher, es führt seine Ajax-Anfrage durch und aktualisiert seinen eigenen lokalen Zustand. Aber wenn andere Bereiche der Anwendung aufgrund der neu abgerufenen Benutzerliste geändert werden müssen, reicht diese Strategie nicht aus.
Mit der Redux-Strategie können wir eine Aktion dispatchen, wenn die Ajax-Anfrage zurückkehrt, anstatt this.setState() auszuführen. Dann können diese Komponente und andere die Zustandsänderung abonnieren. Aber das bringt uns tatsächlich zu der Frage, wie wir store.subscribe() einrichten, um den Zustand der Komponente zu aktualisieren?
Ich nehme an, ich könnte mehrere Beispiele für die manuelle Verdrahtung von Komponenten mit dem Redux Store liefern. Sie können sich wahrscheinlich sogar vorstellen, wie das mit Ihrem eigenen Ansatz aussehen könnte. Aber letztendlich würde ich am Ende dieser Beispiele erklären, dass es einen besseren Weg gibt, und die manuellen Beispiele vergessen. Ich würde dann das offizielle React/Redux-Bindungsmodul namens react-redux vorstellen. Also springen wir gleich dorthin.
Verbinden mit react-redux
Um es klarzustellen: react, redux und react-redux sind drei separate Module auf npm. Das Modul react-redux ermöglicht es uns, React-Komponenten auf bequemere Weise mit Redux zu „verbinden“.
So sieht es aus:
import React from 'react';
import { connect } from 'react-redux';
import store from '../path/to/store';
import axios from 'axios';
import UserList from '../views/list-user';
const UserListContainer = React.createClass({
componentDidMount: function() {
axios.get('/path/to/user-api').then(response => {
store.dispatch({
type: 'USER_LIST_SUCCESS',
users: response.data
});
});
},
render: function() {
return <UserList users={this.props.users} />;
}
});
const mapStateToProps = function(store) {
return {
users: store.userState.users
};
}
export default connect(mapStateToProps)(UserListContainer);
Es gibt viele neue Dinge:
- Wir haben die Funktion
connectausreact-reduximportiert. - Dieser Code ist möglicherweise leichter von unten nach oben zu verfolgen, beginnend mit der Verbindung. Die Funktion
connect()benötigt tatsächlich zwei Argumente, aber wir zeigen nur eines fürmapStateToProps().Es mag seltsam aussehen, die zusätzlichen Klammern für
connect()()zu sehen. Dies sind tatsächlich zwei Funktionsaufrufe. Der erste,connect(), gibt eine weitere Funktion zurück. Ich nehme an, wir hätten dieser Funktion einen Namen zuweisen und sie dann aufrufen können, aber warum das tun, wenn wir sie einfach sofort mit dem zweiten Satz von Klammern aufrufen können? Außerdem würden wir diesen zweiten Funktionsnamen nach dem Aufruf sowieso aus keinem Grund benötigen. Die zweite Funktion erfordert jedoch, dass Sie eine React-Komponente übergeben. In diesem Fall ist es unsere Container-Komponente.Ich verstehe, wenn Sie denken: „Warum es komplizierter machen, als es sein muss?“, aber dies ist tatsächlich ein gängiges „funktionales Programmierparadigma“, daher ist es gut, es zu lernen.
- Das erste Argument für
connect()ist eine Funktion, die ein Objekt zurückgeben sollte. Die Eigenschaften des Objekts werden zu „Props“ in der Komponente. Sie können sehen, dass ihre Werte aus dem Zustand stammen. Jetzt hoffe ich, dass der Funktionsname „mapStateToProps“ mehr Sinn ergibt. Beachten Sie auch, dassmapStateToProps()ein Argument erhält, das der gesamte Redux Store ist. Die Hauptidee vonmapStateToProps()besteht darin, zu isolieren, welche Teile des Gesamtzustands diese Komponente als Props benötigt. - Aus den in #3 genannten Gründen benötigen wir
getInitialState()nicht mehr. Beachten Sie auch, dass wir aufthis.props.usersstatt aufthis.state.usersverweisen, da dasusers-Array jetzt ein Prop und kein lokaler Komponentenstatus ist. - Die Ajax-Rückgabe dispatched jetzt eine Aktion, anstatt den lokalen Komponentenstatus zu aktualisieren. Der Kürze halber verwenden wir keine Action Creator oder Action Type Konstanten.
Das Codebeispiel macht eine Annahme über die Funktionsweise des Benutzer-Reducers, die möglicherweise nicht offensichtlich ist. Beachten Sie, dass der Store die Eigenschaft userState hat. Aber woher kam dieser Name?
const mapStateToProps = function(store) {
return {
users: store.userState.users
};
}
Dieser Name stammte aus der Kombination unserer Reducer
const reducers = combineReducers({
userState: userReducer,
widgetState: widgetReducer
});
Was ist mit der Eigenschaft .users von userState? Woher kam das?
Obwohl wir kein tatsächliches Reducer für das Beispiel gezeigt haben (weil es sich in einer anderen Datei befinden würde), bestimmt der Reducer die Untereigenschaften seines jeweiligen Zustands. Um sicherzustellen, dass .users eine Eigenschaft von userState ist, könnte der Reducer für diese Beispiele so aussehen:
const initialUserState = {
users: []
}
const userReducer = function(state = initialUserState, action) {
switch(action.type) {
case 'USER_LIST_SUCCESS':
return Object.assign({}, state, { users: action.users });
}
return state;
}
Ajax-Lebenszyklus-Dispatches
In unserem Ajax-Beispiel haben wir nur eine Aktion dispatched. Sie wurde absichtlich 'USER_LIST_SUCCESS' genannt, weil wir möglicherweise auch 'USER_LIST_REQUEST' dispatchen möchten, bevor Ajax beginnt, und 'USER_LIST_FAILED' bei einem Ajax-Fehler. Lesen Sie unbedingt die Dokumente zu asynchronen Aktionen.
Dispatchen von Ereignissen
Im vorherigen Artikel haben wir gesehen, dass Ereignisse von Container- zu Präsentationskomponenten weitergegeben werden sollten. Es stellt sich heraus, dass react-redux auch dabei hilft, wenn ein Ereignis einfach eine Aktion dispatchen muss.
...
const mapDispatchToProps = function(dispatch, ownProps) {
return {
toggleActive: function() {
dispatch({ ... });
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserListContainer);
In der Präsentationskomponente können wir onClick={this.props.toggleActive} genau wie zuvor tun, aber dieses Mal mussten wir das Ereignis nicht selbst schreiben.
Auslassung der Container-Komponente
Manchmal muss eine Container-Komponente nur den Store abonnieren und benötigt keine Methoden wie componentDidMount(), um Ajax-Anfragen zu starten. Sie benötigt möglicherweise nur eine render()-Methode, um den Zustand an die Präsentationskomponente weiterzugeben. In diesem Fall können wir eine Container-Komponente auf diese Weise erstellen:
import React from 'react';
import { connect } from 'react-redux';
import UserList from '../views/list-user';
const mapStateToProps = function(store) {
return {
users: store.userState.users
};
}
export default connect(mapStateToProps)(UserList);
Ja, Leute, das ist die ganze Datei für unsere neue Container-Komponente. Aber warten Sie, wo ist die Container-Komponente? Und warum verwenden wir hier kein React.createClass()?
Es stellt sich heraus, dass connect() eine Container-Komponente für uns erstellt. Beachten Sie, dass wir dieses Mal die Präsentationskomponente direkt übergeben, anstatt unsere eigene Container-Komponente zu erstellen, die wir übergeben. Wenn Sie wirklich darüber nachdenken, was Container-Komponenten tun, erinnern Sie sich, dass sie existieren, damit sich die Präsentationskomponente nur auf die Ansicht und nicht auf den Zustand konzentrieren kann. Sie geben den Zustand auch als Props an die untergeordnete Ansicht weiter. Und genau das tut connect() — es übergibt den Zustand (über Props) an unsere Präsentationskomponente und gibt tatsächlich eine React-Komponente zurück, die die Präsentationskomponente umschließt. Im Wesentlichen ist dieser Wrapper eine Container-Komponente.
Heißt das also, dass die Beispiele von zuvor eigentlich zwei Container-Komponenten sind, die eine Präsentationskomponente umschließen? Sicher, Sie können es so sehen. Aber das ist kein Problem, es ist nur notwendig, wenn unsere Container-Komponente mehr React-Methoden als render() benötigt.
Stellen Sie sich vor, die beiden Container-Komponenten dienen unterschiedlichen, aber verwandten Rollen
Hmm, vielleicht sieht das React-Logo deshalb wie ein Atom aus!
Provider
Damit dieser react-redux-Code funktioniert, müssen Sie Ihrer App über eine <Provider />-Komponente mitteilen, wie react-redux verwendet werden soll. Diese Komponente umschließt Ihre gesamte React-Anwendung. Wenn Sie React Router verwenden, würde es so aussehen:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import router from './router';
ReactDOM.render(
<Provider store={store}>{router}</Provider>,
document.getElementById('root')
);
Der an Provider angehängte store verbindet React und Redux wirklich über react-redux. Diese Datei ist ein Beispiel dafür, wie Ihr Haupteinstiegspunkt aussehen könnte.
Redux mit React Router
Es ist nicht erforderlich, aber es gibt ein weiteres npm-Projekt namens react-router-redux. Da Routen technisch gesehen Teil des UI-Zustands sind und React Router nichts über Redux weiß, hilft dieses Projekt, die beiden zu verknüpfen.
Sehen Sie, was ich dort gemacht habe? Wir haben den Kreis geschlossen und sind zurück beim ersten Artikel!
Abschlussprojekt
Die finale Projektanleitung für diese Serie ermöglicht es Ihnen, eine kleine „Users and Widgets“ Single Page App zu erstellen.

Wie bei den anderen Artikeln dieser Serie enthält jeder eine Anleitung, die noch mehr Dokumentation darüber enthält, wie die Anleitung auf GitHub funktioniert.
Zusammenfassung
Ich hoffe wirklich, dass Ihnen diese Serie genauso viel Spaß gemacht hat wie mir beim Schreiben. Mir ist klar, dass wir viele Themen zu React nicht behandelt haben (Formulare zum Beispiel), aber ich habe versucht, der Prämisse treu zu bleiben, dass ich neuen Benutzern von React ein Gefühl dafür vermitteln wollte, wie man über die Grundlagen hinauskommt und wie es sich anfühlt, eine Single Page Application zu erstellen.
Obwohl viele geholfen haben, geht ein besonderer Dank an Lynn Fisher für die erstaunlichen Grafiken, die sie für die Tutorials bereitgestellt hat!
Artikelserie
- React Router
- Container-Komponenten
- Redux (Sie sind hier!)
Hervorragender Artikel.
Toller Artikel!
Übrigens einen Tippfehler entdeckt,
Das war nur, um zu sehen, ob du aufpasst. jk Danke Jeremy
Hab's!
Vielen Dank für das Schreiben dieser Serie. Ich habe vor kurzem angefangen, verschiedene NodeJS-bezogene Technologien zu lernen. Nachdem ich eine Reihe von Tutorials durchgearbeitet habe, kann ich ohne Zweifel sagen... Ihre Artikel sind die hilfreichste Einführung, die ich gefunden habe. Nochmals vielen Dank!
Wooow! Das ist jetzt ein CSSfuckingtrick!
Diese waren brilliant, vielen Dank Brad!
Ein weiterer lustiger Artikel zum Lesen, aber überhaupt kein Fan von Redux. String-Konstanten, die Aktionstyp-Identifikatoren enthalten, gepaart mit Switch-Case-Anweisungen für die eigentliche Logik, fühlen sich für mich wie C oder C++ an, nicht wie idiomatisch JavaScript. Wenn JavaScript wie C oder C++ aussieht, stimmt etwas nicht, lol. Würde gerne ein Beispiel sehen, das mehr nach JavaScript aussieht :)
Ich bin auch kein Fan der String-Konstanten-Strategie, aber es ist die gängigste, die ich sehe, also dachte ich, ich mache mit. Ich denke, es vermittelt die Grundlagen dessen, was wir versuchen zu tun. Ich bin ein großer Fan von
redux-act, was ein anderer Ansatz ist. Es ist ein Projekt, das den Schmerz des Boilerplate-Krams mit Aktionstypen und Action Creatorn lindert. Und es verwendet nicht den String-Konstanten-Ansatz, tatsächlich abstrahiert es diesen gesamten Prozess von Ihnen, was schön ist. Ich würde Redux dafür nicht die Schuld geben. Es ist nur eine von vielen Strategien, die verwendet werden könnten, die wenig mit dem zu tun hat, was Redux bietet. Die Hauptidee, ich muss etwas dispatchen und der richtige Reducer muss davon wissen – jede Strategie, die Sie erfinden können, die das tut, ist in Ordnung.fantastisch! Mein Gehirn muss viel verarbeiten, aber ich werde ein Testprojekt starten und versuchen, diese Konzepte anzuwenden. Vielen Dank, dass Sie sich die Zeit genommen haben, einen so guten Artikel zu schreiben!
Danke Victor, und auch Mike, Balazs und Darrly
Hi. Warum verwenden Sie für den Aufruf // Dispatch our first action to express an intent to change the state in the #Our first Redux Store das Schlüsselwort VAR
var store.dispatch({
type: ‘ADD_USER’,
user: {name: ‘Dan’}
});
Hi Tim, es tut mir leid, aber ich verstehe die Frage nicht.
Entschuldigung für mein Englisch.
im Beispielcode, der für den Teil #Our first Redux Store geschrieben wurde
am Ende steht
var store.dispatch({
type: ‘ADD_USER’,
user: {name: ‘Dan’}
});
Ich versuche nur zu verstehen, warum „var“ vor dem Aufruf einer Funktion store.dispatch steht
Ich habe versehentlich einen ganz neuen Kommentar anstelle einer Antwort erstellt. Siehe unten
Oh! Das ist ein Tippfehler, ich werde sehen, ob Chris ihn entfernen kann. Ich kann sehen, warum das verwirrend wäre, es ist nicht richtig. Sie sind der Erste, der darauf hinweist, von allen, die es überprüft haben :)
Oh danke. Jetzt verstehe ich es )
Eigentlich bin ich so verwirrt mit Redux. Im zweiten Bild wird der Baum der Komponenten gezeigt. Wie rufe ich das Ändern des Stores von der untersten Dump-Komponente aus auf oder initiiere es, ohne es durch den gesamten Baum zu werfen? Oder ist es besser, es nicht zu tun? Und wenn ja, warum nicht?
Ich werde „presentational“ (präsentational) anstelle von „dumb“ (dumm) sagen, da die Branche es so bezeichnen möchte. Aber nehmen wir an, die präsentationale Komponente möchte eine Änderung initiieren. Normalerweise ist die Notwendigkeit, den Zustand zu ändern, auf ein Ereignis zurückzuführen. Basierend auf dem letzten Artikel können wir uns daran erinnern, dass wir Ereignisse von der Containerkomponente an die präsentationale Komponente weitergeben können. Während also die präsentationale Komponente tatsächlich das
onClickverarbeitet, ruft sie eine Funktion auf, die sich in der Containerkomponente befindet. Der Container befasst sich mit dem Zustand, daher führt die durch dasonClick(im Container) aufgerufene Funktion dasstore.dispatch(...)durch.Der Demo-Code auf GitHub zeigt dies. Hier ist die präsentationale „View“-Komponente, die einen Klick verarbeitet Beispiel hier
Hier ist die Containerkomponente, in der
deleteUserauf eine Funktion verweist, die ein API-Aufruf ist: Beispiel hierDiese Containerkomponente hätte so geschrieben werden können
Ich habe dies jedoch nicht in meinem Code gemacht, weil ich einen XHR-Aufruf durchführen möchte, bevor ich dispatche, weshalb der echte Code
userApi.deleteUseraufruft, damit er den XHR durchführen kann, um dann beim erfolgreichen Rücklauf zu dispatchen. Soweit ich gesehen habe, gibt es verschiedene Strategien für XHR und Store-Dispatches. Das ist nur eine einfache Methode. Aber hoffentlich gibt Ihnen der Code etwas mehr Kontext, den der Artikel nur streift.Ich verstehe nicht, warum ich die präsentationale Komponente in den Container einwickeln muss. Darf ich Sie bitten, es noch einmal zu erklären?
Und noch eine Frage:
Ich habe zum Beispiel einen großen Komponentenbaum mit 10 Ebenen, und zum Beispiel mit dem Aktionsbutton wird nur im letzten Komponenten aller Ebenen der Zustand angezeigt. Muss ich in diesem Fall eine Ereignisaktion von der Spitze des Baums, an dem ich mit dem Store verbunden bin, nach unten fließen lassen oder was? Vielen Dank für Ihre Hilfe. Ihre Beiträge zu React sind für mich von großer Bedeutung.
Kann ich davon ausgehen, dass Sie den vorherigen Artikel über Containerkomponenten gelesen haben? Die Prämisse ist, dass die präsentationalen (die Ansichten) nicht über den Zustand Bescheid wissen sollen. Dies macht sie wiederverwendbarer. Stellen Sie sich vor, die präsentationale Ansicht ist eine Bestätigungsaufforderung. Wenn sie zustandslos ist und den Zustand nur von Containern erhält, kann sie mit mehreren Containerkomponenten für verschiedene Zustandskontexte wiederverwendet werden. Als bewährte Praxis wollen wir die präsentationalen Komponenten also immer zustandsfrei halten.
Bei Ihrer zweiten Frage, wenn ich es richtig verstehe, könnten Sie 10 Nachfolger haben, wie Sie erwähnen, wobei der unterste das tatsächliche Ereignis erfasst und die übergeordnetste Komponente der Container ist, der den Zustand kontrolliert – dann könnten Sie den Ereignishandler die Linie hinunter weitergeben. Achten Sie in diesem Fall darauf, sich Spread-Attribute anzusehen, sie erleichtern das Weitergeben von Props von Eltern zu Kind zu Kind usw. – ODER – Ich bin mir nicht sicher, warum Sie 10 verschachtelte Komponenten haben, aber wissen Sie einfach, dass im Allgemeinen die meisten präsentationalen View-Komponenten einen Container haben, der dazugehört. Sie müssen also nicht nur einen an der Spitze dieser langen Nachfolgerliste von Ansichten haben. Sie könnten einen Container haben, der eine Ansicht hat, dann hat diese Ansicht eine untergeordnete Komponente, die ein Container ist, der eine Ansicht hat. Für meinen Schreibstil von React wäre es also ungewöhnlich, viele verschachtelte Ansichten ohne Containerkomponenten in der Mischung zu haben. Denken Sie dann daran, dass jede Containerkomponente mit dem Zustand kommunizieren kann. Ich hoffe wirklich, dass das Sinn macht :)
Ich denke, Sie sind möglicherweise an dem Punkt, an dem Sie architektonische Fragen haben, auf die Sie in Artikeln wahrscheinlich keine einfachen Antworten finden werden. Als ich in diesem Zustand war – bin es immer noch – fand ich es schön, mir tonnenweise Beispielcode oder Boilerplate-React-Projekte auf GitHub anzusehen, um zu sehen, wie sie es gemacht haben. Ich würde sicherstellen, dass Sie sich auch den GitHub-Code ansehen, den ich für diesen Artikel gepostet habe.
Oh, wiederverwendbar!!! Natürlich!!! Das erklärt viel!
Aber zweitens, in diesem Fall ist es besser ohne Redux, ich kann einfach den gesamten Zustand in einem Hauptcontainer behalten, und das war's, die App wird mit reinem React schneller sein. Wahrscheinlich bin ich für Redux noch nicht reif genug.
Vielen Dank für die Artikel, für mich war es eine große Hilfe. Besonders über Komponenten.
Hi Brad... Toller Artikel. Darf ich wissen, welches Tool Sie zum Erstellen der Diagramme verwenden? Danke.
Eine Freundin hat sie für mich gemacht. Sie wird am Ende in der Zusammenfassung genannt.
Einer der besten Artikel, die ich zu diesem Thema gelesen habe! Wirklich gut erklärt, danke!
Dies ist wahrscheinlich der beste Artikel, um mit dem Lernen von React zu beginnen. Vielen Dank für die tollen Artikel.
Ich stimme voll und ganz zu, ich habe tagelang gelesen und versucht, das Redux-Konzept zu verstehen. Diese Erklärung ist simpel und genau richtig. Jetzt macht alles Sinn und hat mich wirklich dazu gebracht, Redux zu lieben.
Hi Brad,
Vielen Dank für diese großartige Serie.
Ich habe eine Frage: Welche Probleme könnten auftreten, wenn ich eine Website ohne Redux erstelle?
Ist Redux ein Muss?
Ich muss die Website so schnell wie möglich fertigstellen. Vielen Dank im Voraus!
Hi Brad,
Zunächst einmal, tolle Artikel. Dies ist der erste und einzige Ort, an dem ich eine größere Anwendung in reactjs und react-router finden konnte.
Ich verwende React und React-Router wie in den ersten 2 Tutorials.
Muss ich Redux auch für meine Website verwenden?
Ich muss so schnell wie möglich eine SPA erstellen, welche Probleme könnten also auftreten, wenn ich es ohne Redux mache?
Vielen Dank im Voraus!
Nun, Redux ist sicherlich nicht für eine React-App erforderlich, aber eine Form des Zustandsmanagements ist für jede App-Größe erforderlich. Die Verwendung des lokalen Zustandsmanagements (Reacts integrierter Zustandsmechanismus für jede Komponente) ist schön, aber für mich finde ich, dass die Verwendung von Redux für eine größere Anwendung noch schöner ist, weil alle Komponenten den Zustand teilen können. Wenn Sie Redux (oder etwas Ähnliches) nicht verwenden, wie verwalten Sie den Zustand auf App-Ebene?
Vielen Dank, Brad,
Schöner Artikel, schöne Bilder, schönes CSS ... alles schön.
Aber ich möchte ein wenig weiter gehen.
Nur um zu fragen, ob es einen nächsten Artikel geben wird, in dem Sie Ihre Sachen auf mdl (Material Design Lite) portieren (diese Website ist css-tricks!), oder andere Material Design CSS-Frameworks.
Ich bin wirklich frustriert über meine eigene Arbeit, diesen Artikel und den React-Router mit dem mdl-Framework zusammenzuführen.
Ich habe die
index.htmlwie im Beispiel auf index eingerichtet und aufmain-layout.jshabe ich einige der Beispiele wie dieses übersetzt, aber ich weiß nicht, wie ich das Menü mit derLink-Komponente von react-router verknüpfe und das Ergebnis auf der „Container-Seite“ anzeige.Vielen Dank im Voraus
Hi Santi, tatsächlich habe ich keine Erfahrung mit Material Design mit React. Diese Serie von mir ist mit diesem letzten Artikel so ziemlich abgeschlossen. Aber ich frage mich, ob Chris jemanden finden kann, der einen Artikel über das schreibt, worüber Sie sprechen. Tut mir leid, dass ich nicht mehr helfen konnte.
Dieser Blogbeitrag wurde im React Newsletter #25 vorgestellt. http://eepurl.com/bWc4Wf