Ich erstelle eine react.js-Anwendung mit Flussarchitektur und versuche herauszufinden, wo und wann eine Anforderung von Daten vom Server erfolgen soll. Gibt es dafür ein Beispiel? (Nicht TODO App!)
Ich erstelle eine react.js-Anwendung mit Flussarchitektur und versuche herauszufinden, wo und wann eine Anforderung von Daten vom Server erfolgen soll. Gibt es dafür ein Beispiel? (Nicht TODO App!)
Antworten:
Ich bin ein großer Befürworter von asynchronen Schreibvorgängen in den Aktionserstellern und asynchronen Lesevorgängen im Geschäft. Ziel ist es, den Änderungscode für den Speicherstatus in vollständig synchronen Aktionshandlern beizubehalten. Dies macht sie einfach zu überlegen und einfach zu testen. Um zu verhindern, dass mehrere Anforderungen gleichzeitig an denselben Endpunkt gesendet werden (z. B. doppeltes Lesen), verschiebe ich die eigentliche Anforderungsverarbeitung in ein separates Modul, das Versprechen verwendet, um die mehreren Anforderungen zu verhindern. beispielsweise:
class MyResourceDAO {
get(id) {
if (!this.promises[id]) {
this.promises[id] = new Promise((resolve, reject) => {
// ajax handling here...
});
}
return this.promises[id];
}
}
Während Lesevorgänge im Speicher asynchrone Funktionen beinhalten, gibt es eine wichtige Einschränkung, dass die Speicher sich nicht in den asynchronen Handlern aktualisieren, sondern stattdessen eine Aktion auslösen und erst dann eine Aktion auslösen, wenn die Antwort eintrifft. Handler für diese Aktion führen am Ende die eigentliche Statusänderung durch.
Eine Komponente kann beispielsweise Folgendes tun:
getInitialState() {
return { data: myStore.getSomeData(this.props.id) };
}
Der Laden würde eine Methode implementieren, vielleicht so etwas:
class Store {
getSomeData(id) {
if (!this.cache[id]) {
MyResurceDAO.get(id).then(this.updateFromServer);
this.cache[id] = LOADING_TOKEN;
// LOADING_TOKEN is a unique value of some kind
// that the component can use to know that the
// value is not yet available.
}
return this.cache[id];
}
updateFromServer(response) {
fluxDispatcher.dispatch({
type: "DATA_FROM_SERVER",
payload: {id: response.id, data: response}
});
}
// this handles the "DATA_FROM_SERVER" action
handleDataFromServer(action) {
this.cache[action.payload.id] = action.payload.data;
this.emit("change"); // or whatever you do to re-render your app
}
}
flux
wird es nach dem Bau in die Läden injiziert, so dass es keine gute Möglichkeit gibt, Aktionen in der Initialisierungsmethode zu erhalten. Vielleicht finden Sie einige gute Ideen aus den isomorophischen Flussbibliotheken von Yahoo. Dies sollte Fluxxor v2 besser unterstützen. Sie können mir gerne eine E-Mail senden, wenn Sie mehr darüber chatten möchten.
data: result
sollte sein data : data
, richtig? es gibt keine result
. Vielleicht ist es besser, den Datenparameter in Payload oder ähnliches umzubenennen.
Fluxxor bietet ein Beispiel für die asynchrone Kommunikation mit einer API.
Dieser Blog-Beitrag hat Gespräche darüber und wurde in Reacts Blog vorgestellt.
Ich finde dies eine sehr wichtige und schwierige Frage, die noch nicht klar beantwortet ist, da die Synchronisierung der Frontend-Software mit dem Backend immer noch schmerzhaft ist.
Sollten API-Anforderungen in JSX-Komponenten gestellt werden? Shops? Anderer Ort?
Das Ausführen von Anforderungen in Geschäften bedeutet, dass zwei Geschäfte, die für eine bestimmte Aktion dieselben Daten benötigen, zwei ähnliche Anforderungen ausgeben (es sei denn, Sie führen Abhängigkeiten zwischen Geschäften ein, die mir wirklich nicht gefallen ).
In meinem Fall habe ich dies als sehr praktisch empfunden, um Q-Versprechen als Nutzlast von Aktionen zu verwenden, weil:
Ajax ist böse
Ich denke, Ajax wird in naher Zukunft immer weniger verwendet, da es sehr schwer zu überlegen ist. Der richtige Weg? Wenn ich Geräte als Teil des verteilten Systems betrachte, weiß ich nicht, wo ich zum ersten Mal auf diese Idee gestoßen bin (vielleicht in diesem inspirierenden Chris Granger-Video ).
Denk darüber nach. Aus Gründen der Skalierbarkeit verwenden wir jetzt verteilte Systeme mit eventueller Konsistenz als Speicher-Engines (weil wir den CAP-Satz nicht übertreffen können und häufig verfügbar sein möchten). Diese Systeme werden nicht durch gegenseitige Abfragen synchronisiert (außer vielleicht für Konsensoperationen?), Sondern verwenden Strukturen wie CRDT und Ereignisprotokolle, um alle Mitglieder des verteilten Systems schließlich konsistent zu machen (Mitglieder konvergieren bei ausreichender Zeit zu denselben Daten). .
Denken Sie nun darüber nach, was ein mobiles Gerät oder ein Browser ist. Es ist nur ein Mitglied des verteilten Systems, das unter Netzwerklatenz und Netzwerkpartitionierung leiden kann.(dh Sie benutzen Ihr Smartphone in der U-Bahn)
Wenn wir Netzwerkpartitions- und Netzwerkgeschwindigkeitstolerante Datenbanken erstellen können (ich meine, wir können weiterhin Schreibvorgänge auf einem isolierten Knoten ausführen), können wir wahrscheinlich Frontend-Software (mobil oder Desktop) erstellen, die von diesen Konzepten inspiriert ist und gut mit dem unterstützten Offline-Modus funktioniert der Box ohne App-Funktionen nicht verfügbar.
Ich denke, wir sollten uns wirklich davon inspirieren lassen, wie Datenbanken zur Architektur unserer Frontend-Anwendungen funktionieren. Zu beachten ist, dass diese Apps keine POST- und PUT- und GET-Ajax-Anforderungen ausführen, um Daten aneinander zu senden, sondern Ereignisprotokolle und CRDT verwenden, um eine eventuelle Konsistenz sicherzustellen.
Warum also nicht das im Frontend? Beachten Sie, dass sich das Backend bereits in diese Richtung bewegt und Tools wie Kafka von Big Playern massiv übernommen werden. Dies hängt auch irgendwie mit Event Sourcing / CQRS / DDD zusammen.
Schauen Sie sich diese großartigen Artikel von Kafka-Autoren an, um sich selbst zu überzeugen:
Vielleicht können wir damit beginnen, Befehle an den Server zu senden und einen Strom von Serverereignissen zu empfangen (zum Beispiel über Websockets), anstatt Ajax-Anfragen auszulösen.
Ich habe mich mit Ajax-Anfragen noch nie sehr wohl gefühlt. Während wir reagieren, sind Entwickler in der Regel funktionale Programmierer. Ich denke, es ist schwierig, über lokale Daten nachzudenken, die Ihre "Quelle der Wahrheit" Ihrer Frontend-Anwendung sein sollen, während die wahre Quelle der Wahrheit tatsächlich in der Serverdatenbank liegt und Ihre "lokale" Quelle der Wahrheit möglicherweise bereits veraltet ist wenn Sie es erhalten und niemals zur wahren Quelle des Wahrheitswertes konvergieren, es sei denn, Sie drücken eine lahme Aktualisierungstaste ... Ist das Engineering?
Es ist jedoch aus offensichtlichen Gründen immer noch ein bisschen schwierig, so etwas zu entwerfen:
this.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Sie können Daten entweder in den Aktionserstellern oder in den Filialen abrufen. Das Wichtigste ist, die Antwort nicht direkt zu verarbeiten, sondern eine Aktion im Fehler- / Erfolgsrückruf zu erstellen. Die Handhabung der Antwort direkt im Geschäft führt zu einem spröderen Design.
Ich habe das Beispiel von Binary Muse aus dem Fluxxor Ajax-Beispiel verwendet . Hier ist mein sehr einfaches Beispiel mit demselben Ansatz.
Ich habe einen einfachen Produktspeicher mit einigen Produktaktionen und die Controller-Ansichtskomponente mit Unterkomponenten, die alle auf Änderungen am Produktspeicher reagieren . Zum Beispiel Produkt-Slider- , Produktlisten- und Produktsuchkomponenten .
Fake Product Client
Hier ist der gefälschte Client, den Sie ersetzen können, wenn Sie einen tatsächlichen Endpunkt aufrufen, der Produkte zurückgibt.
var ProductClient = {
load: function(success, failure) {
setTimeout(function() {
var ITEMS = require('../data/product-data.js');
success(ITEMS);
}, 1000);
}
};
module.exports = ProductClient;
Produktspeicher
Hier ist der Product Store, offensichtlich ist dies ein sehr minimaler Store.
var Fluxxor = require("fluxxor");
var store = Fluxxor.createStore({
initialize: function(options) {
this.productItems = [];
this.bindActions(
constants.LOAD_PRODUCTS_SUCCESS, this.onLoadSuccess,
constants.LOAD_PRODUCTS_FAIL, this.onLoadFail
);
},
onLoadSuccess: function(data) {
for(var i = 0; i < data.products.length; i++){
this.productItems.push(data.products[i]);
}
this.emit("change");
},
onLoadFail: function(error) {
console.log(error);
this.emit("change");
},
getState: function() {
return {
productItems: this.productItems
};
}
});
module.exports = store;
Jetzt lösen die Produktaktionen, die die AJAX-Anforderung auslösen, bei Erfolg die Aktion LOAD_PRODUCTS_SUCCESS aus, mit der Produkte an den Store zurückgegeben werden.
Produktaktionen
var ProductClient = require("../fake-clients/product-client");
var actions = {
loadProducts: function() {
ProductClient.load(function(products) {
this.dispatch(constants.LOAD_PRODUCTS_SUCCESS, {products: products});
}.bind(this), function(error) {
this.dispatch(constants.LOAD_PRODUCTS_FAIL, {error: error});
}.bind(this));
}
};
module.exports = actions;
Also anrufen this.getFlux().actions.productActions.loadProducts()
von einer Komponente aus , die diesen Speicher abhört, werden die Produkte geladen.
Sie können sich jedoch vorstellen, dass verschiedene Aktionen auf Benutzerinteraktionen wie addProduct(id)
removeProduct(id)
etc ... nach demselben Muster reagieren .
Ich hoffe, dieses Beispiel hilft ein bisschen, da ich die Implementierung als etwas schwierig empfand, aber sicherlich dazu beigetragen habe, meine Geschäfte zu 100% synchron zu halten.
Ich habe hier eine verwandte Frage beantwortet: Wie man verschachtelte API-Aufrufe im Fluss behandelt
Handlungen sollen keine Dinge sein, die eine Veränderung bewirken. Sie sollen wie eine Zeitung sein, die die Anwendung über eine Veränderung in der Außenwelt informiert, und dann reagiert die Anwendung auf diese Nachrichten. Die Geschäfte bewirken Veränderungen an sich. Aktionen informieren sie nur.
Bill Fisher, Erfinder von Flux https://stackoverflow.com/a/26581808/4258088
Grundsätzlich sollten Sie über Aktionen angeben, welche Daten Sie benötigen. Wenn der Speicher von der Aktion informiert wird, sollte er entscheiden, ob er Daten abrufen muss.
Der Speicher sollte für das Sammeln / Abrufen aller erforderlichen Daten verantwortlich sein. Es ist jedoch wichtig zu beachten, dass der Speicher, nachdem er die Daten angefordert und die Antwort erhalten hat, selbst eine Aktion mit den abgerufenen Daten auslösen sollte, im Gegensatz dazu, dass der Speicher die Antwort direkt verarbeitet / speichert.
Ein Geschäft könnte ungefähr so aussehen:
class DataStore {
constructor() {
this.data = [];
this.bindListeners({
handleDataNeeded: Action.DATA_NEEDED,
handleNewData: Action.NEW_DATA
});
}
handleDataNeeded(id) {
if(neededDataNotThereYet){
api.data.fetch(id, (err, res) => {
//Code
if(success){
Action.newData(payLoad);
}
}
}
}
handleNewData(data) {
//code that saves data and emit change
}
}
Hier ist meine Meinung dazu: http://www.thedreaming.org/2015/03/14/react-ajax/
Hoffentlich hilft das. :) :)