Wo soll eine Ajax-Anfrage in der Flux-App gestellt werden?


194

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:


127

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
  }
}

Haben Sie versucht, Versprechungen in Aktionsnutzlasten zu setzen? Ich finde es einfacher, damit umzugehen, als mehrere Aktionen
Sebastien Lorber

@SebastienLorber Der große Vorteil für mich besteht darin, alle Statusaktualisierungen in einem synchronen Codepfad zu halten, und zwar explizit nur als Ergebnis von Aktionsversendungen, sodass ich Asynchronität innerhalb der Geschäfte vermeide.
Michelle Tilley

1
@Federico Mir ist immer noch unklar, was die "beste" Lösung ist. Ich habe mit dieser Strategie zum Laden von Daten experimentiert und gleichzeitig die Anzahl der ausstehenden asynchronen Anforderungen gezählt. Leider fluxwird 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.
Michelle Tilley

1
data: resultsollte sein data : data, richtig? es gibt keine result. Vielleicht ist es besser, den Datenparameter in Payload oder ähnliches umzubenennen.
Oligofren

2
Ich fand diesen alten Thread sehr hilfreich - insbesondere die Kommentare von Bill Fisher und Jing Chen. Dies kommt dem, was @BinaryMuse vorschlägt, sehr nahe, mit dem kleinen Unterschied, dass das Versenden im Aktionsersteller erfolgt.
Phillipwei

37

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:

  • Meine Aktionen müssen nicht serialisierbar sein (ich führe kein Ereignisprotokoll, ich benötige keine Ereigniswiedergabefunktion für die Ereignisbeschaffung).
  • Es entfällt die Notwendigkeit, verschiedene Aktionen / Ereignisse zu haben (Anforderung ausgelöst / Anforderung abgeschlossen / Anforderung fehlgeschlagen) und diese mithilfe von Korrelations-IDs abzugleichen, wenn gleichzeitige Anforderungen ausgelöst werden können.
  • Es ermöglicht mehreren Speichern, den Abschluss derselben Anforderung abzuhören, ohne dass eine Abhängigkeit zwischen den Speichern entsteht (es ist jedoch möglicherweise besser, eine Caching-Schicht einzuführen?).

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:

  • Ihr Mobil- / Browser-Client verfügt nur über begrenzte Ressourcen und kann nicht unbedingt alle Daten lokal speichern (daher ist manchmal eine Abfrage mit einer Ajax-Anfrage erforderlich, die umfangreiche Inhalte enthält).
  • Ihr Client sollte nicht alle Daten des verteilten Systems sehen, daher muss er die Ereignisse, die er aus Sicherheitsgründen empfängt, irgendwie filtern

3
Können Sie ein Beispiel für die Verwendung von Q-Versprechen mit Aktionen geben?
Matt Foxx Duncan

@MattFoxxDuncan ist sich nicht sicher, ob es eine so gute Idee ist, da es das "Ereignisprotokoll" unserialisierbar macht und die Speicheraktualisierung bei ausgelösten Aktionen asynchron macht, sodass es einige Nachteile hat. Wenn es jedoch für Ihren Anwendungsfall in Ordnung ist und Sie diese Nachteile verstehen, ist es recht praktisch und Kesselplatte reduzieren. Mit Fluxxor können Sie wahrscheinlich so etwas tunthis.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Sebastien Lorber

Stimmen Sie Ihrem AJAX-Argument überhaupt nicht zu. Tatsächlich war es sehr nervig zu lesen. Hast du deine Bemerkungen gelesen? Denken Sie an Geschäfte, Spiele und Apps, die ernsthaft Geld verdienen - alle erfordern API- und AJAX-Serveraufrufe. Schauen Sie sich Firebase an, wenn Sie "serverlos" oder etwas Ähnliches wollen, aber AJAX ist hier, um zu sagen, dass ich hoffe, dass zumindest niemand anderes damit einverstanden ist Ihre Logik
TheBlackBenzKid

@TheBlackBenzKid Ich sage nicht, dass Ajax im Laufe des Jahres vollständig verschwinden wird (und ich bin mir sicher, dass ich immer noch Websites auf Ajax-Anfragen als CTO eines Startups aufbaue), aber ich sage, dass es wahrscheinlich verschwinden wird, weil Es ist kein Protokoll, das gut genug ist, um eventuelle Konsistenz zu verarbeiten, die eher Streaming und keine Abfrage erfordert, und eventuelle Konsistenz ermöglicht es, Apps zuverlässig offline zu betreiben (ja, Sie können selbst etwas mit localstorage hacken, aber Sie haben begrenzte Offline-Kapazitäten oder Ihre App ist sehr einfach). Das Problem ist nicht das Zwischenspeichern, sondern das Ungültigmachen dieses Zwischenspeichers.
Sebastien Lorber

@TheBlackBenzKid Die Modelle hinter Firebase, Meteor usw. sind nicht gut genug. Wissen Sie, wie diese Systeme mit gleichzeitigen Schreibvorgängen umgehen? Last-Write-Win statt kausaler Konsistenz- / Zusammenführungsstrategien? Können Sie es sich leisten, die Arbeit Ihres Kollegen in einer App zu überschreiben, wenn beide an unzuverlässigen Verbindungen arbeiten? Beachten Sie auch, dass diese Systeme dazu neigen, die lokalen und Servermodellierungen stark zu koppeln. Kennen Sie eine bekannte kollaborative App, die sehr komplex ist, offline perfekt funktioniert und sich als zufriedener Firebase-Benutzer ausweist? Ich nicht
Sebastien Lorber

20

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.


9
Können Sie dies bitte näher erläutern? Angenommen, ich muss die ersten Daten vom Server laden. In der Controller-Ansicht starte ich eine Aktion INIT, und der Store startet die asynchrone Initialisierung, die diese Aktion widerspiegelt. Nun würde ich mit der Idee gehen, dass der Store, wenn er die Daten abruft, einfach Änderungen ausgibt, aber keine Aktion startet. Wenn Sie also nach der Initialisierung eine Änderung ausgeben, werden die Ansichten darüber informiert, dass sie die Daten aus dem Speicher abrufen können. Warum muss beim erfolgreichen Laden keine Änderung vorgenommen werden, sondern eine andere Aktion gestartet werden?! Vielen Dank
Jim-Y

Fisherwebdev, über Geschäfte, die Daten anfordern, brechen Sie damit nicht das Flux-Paradigma. Die einzigen zwei geeigneten Möglichkeiten, Daten abzurufen, sind: 1. Verwenden Sie eine Bootstrap-Klasse mit Actions zum Laden von Daten 2 Ansichten, wieder mit Aktionen zum Laden von Daten
Yotam

4
Das Abrufen von Daten ist nicht dasselbe wie das Empfangen von Daten. @ Jim-Y: Sie sollten Änderungen erst dann ausgeben, wenn sich die Daten im Geschäft tatsächlich geändert haben. Yotam: Nein, das Aufrufen von Daten im Geschäft bricht nicht das Paradigma. Daten sollten nur durch Aktionen empfangen werden, damit alle Geschäfte über neue Daten informiert werden können, die in die Anwendung eingehen. Wir können also Daten in einem Geschäft abrufen, aber wenn die Antwort zurückkommt, müssen wir eine neue Aktion erstellen, anstatt sie direkt zu verarbeiten. Dies hält die Anwendung flexibel und widerstandsfähig gegenüber der Entwicklung neuer Funktionen.
Fisherwebdev

2

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.


2

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
  }
}

0

Hier ist meine Meinung dazu: http://www.thedreaming.org/2015/03/14/react-ajax/

Hoffentlich hilft das. :) :)


8
Abstimmung gemäß den Richtlinien. Das Platzieren von Antworten auf externen Websites macht diese Website weniger nützlich und führt zu Antworten von geringerer Qualität, wodurch die Nützlichkeit der Website verringert wird. Externe URLs werden wahrscheinlich auch mit der Zeit kaputt gehen. Die Abstimmung sagt nichts über die Nützlichkeit des Artikels aus, was übrigens sehr gut ist :)
Oligofren

2
Guter Beitrag, aber wenn Sie eine kurze Zusammenfassung der Vor- und Nachteile der einzelnen Ansätze hinzufügen, erhalten Sie positive Stimmen. Auf SO sollten wir nicht auf einen Link klicken müssen, um den Kern Ihrer Antwort zu erhalten.
Cory House
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.