Dove dovrebbe essere fatta la richiesta Ajax nell'app Flux?


194

Sto creando un'applicazione React.js con architettura Flux e sto cercando di capire dove e quando una richiesta di dati dal server dovrebbe essere fatta. C'è un esempio per questo. (Non l'app TODO!)

Risposte:


127

Sono un grande sostenitore di mettere operazioni di scrittura asincrona nei creatori di azioni e operazioni di lettura asincrona nel negozio. L'obiettivo è mantenere il codice di modifica dello stato dell'archivio in gestori di azioni completamente sincroni; questo li rende semplici da ragionare e semplici da testare. Al fine di impedire più richieste simultanee allo stesso endpoint (ad esempio, doppia lettura), sposterò l'elaborazione della richiesta effettiva in un modulo separato che utilizza le promesse per impedire le richieste multiple; per esempio:

class MyResourceDAO {
  get(id) {
    if (!this.promises[id]) {
      this.promises[id] = new Promise((resolve, reject) => {
        // ajax handling here...
      });
    } 
    return this.promises[id];
  }
}

Mentre si legge nel negozio coinvolgono funzioni asincrone, v'è un avvertimento importante che i negozi non si aggiornano nei gestori asincroni, ma invece sparano un'azione e solo fuoco un'azione quando la risposta arriva. I gestori di questa azione finiscono per apportare la modifica dello stato effettivo.

Ad esempio, un componente potrebbe fare:

getInitialState() {
  return { data: myStore.getSomeData(this.props.id) };
}

Il negozio avrebbe implementato un metodo, forse, qualcosa del genere:

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

Hai provato a mettere le promesse nei payload di azione? Trovo più facile da gestire che inviare più azioni
Sebastien Lorber,

@SebastienLorber Il grande richiamo al flusso per me è mantenere tutti gli aggiornamenti di stato in un percorso di codice sincrono, ed esplicitamente solo a seguito di invii di azioni, quindi evito l'asincronia all'interno dei negozi.
Michelle Tilley,

1
@Federico Non mi è ancora chiaro quale sia la soluzione "migliore". Ho sperimentato questa strategia per il caricamento dei dati unita al conteggio del numero di richieste asincrone in sospeso. Purtroppo fluxè stato iniettato nei negozi dopo la costruzione, quindi non c'è un ottimo modo per ottenere azioni nel metodo di inizializzazione. Potresti trovare alcune buone idee dalle librerie di flusso isomoropico di Yahoo; questo è qualcosa che Fluxxor v2 dovrebbe supportare meglio. Sentitevi liberi di inviarmi un'e-mail se volete chattare di più su questo.
Michelle Tilley,

1
data: resultdovrebbe essere data : datavero? non c'è result. forse meglio rinominare il parametro di dati in payload o qualcosa del genere.
oligofren

2
Ho trovato molto utile questo vecchio thread , in particolare i commenti di Bill Fisher e Jing Chen. Questo è molto vicino a ciò che @BinaryMuse propone con la minima differenza che l'invio avviene nel creatore dell'azione.
phillipwei,

37

Fluxxor ha un esempio di comunicazione asincrona con un'API.

Questo post sul blog ne parla ed è stato pubblicato sul blog di React.


Trovo che questa sia una domanda molto importante e difficile a cui non è ancora stata data una risposta chiara, poiché la sincronizzazione del software front-end con il back-end è ancora un problema.

Le richieste API devono essere fatte nei componenti JSX? I negozi? Un altro posto?

L'esecuzione delle richieste nei negozi significa che se 2 negozi necessitano degli stessi dati per una determinata azione, emetteranno 2 requisiti simili (a meno che non si introducano dipendenze tra negozi, cosa che non mi piace davvero )

Nel mio caso, l'ho trovato molto utile per mettere le promesse Q come payload di azioni perché:

  • Non è necessario che le mie azioni siano serializzabili (non tengo un registro eventi, non ho bisogno della funzione di replay di eventi per l'approvvigionamento di eventi)
  • Elimina la necessità di avere azioni / eventi diversi (richiesta attivata / richiesta completata / richiesta non riuscita) e deve corrispondere utilizzando gli ID di correlazione quando è possibile generare richieste simultanee.
  • Permette a più negozi di ascoltare il completamento della stessa richiesta, senza introdurre alcuna dipendenza tra i negozi (tuttavia potrebbe essere meglio introdurre un livello di cache?)

Ajax è MALE

Penso che l'Ajax sarà sempre meno utilizzato nel prossimo futuro perché è molto difficile ragionare. Il modo giusto? Considerando i dispositivi come parte del sistema distribuito, non so dove mi sono imbattuto per la prima volta in questa idea (forse in questo video ispiratore di Chris Granger ).

Pensaci. Ora per la scalabilità utilizziamo sistemi distribuiti con eventuale coerenza come motori di archiviazione (perché non possiamo battere il teorema di CAP e spesso vogliamo essere disponibili). Questi sistemi non si sincronizzano tramite il polling reciproco (tranne forse per le operazioni di consenso?), Ma piuttosto utilizzano strutture come CRDT e registri eventi per rendere tutti i membri del sistema distribuito eventualmente coerenti (i membri convergeranno agli stessi dati, con un tempo sufficiente) .

Ora pensa a cosa è un dispositivo mobile o un browser. È solo un membro del sistema distribuito che può soffrire di latenza di rete e partizionamento di rete. (cioè stai usando il tuo smartphone in metropolitana)

Se siamo in grado di costruire database di partizione di rete e tolleranti della velocità di rete (intendo che possiamo ancora eseguire operazioni di scrittura su un nodo isolato), probabilmente possiamo costruire software di frontend (mobile o desktop) ispirati a questi concetti, che funzionano bene con la modalità offline supportata della confezione senza l'app non è disponibile.

Penso che dovremmo davvero ispirarci su come i database stanno lavorando per l'architettura delle nostre applicazioni frontend. Una cosa da notare è che queste app non eseguono le richieste ajax POST e PUT e GET per inviare dati l'un l'altro, ma piuttosto utilizzano i registri eventi e CRDT per garantire la coerenza finale.

Quindi perché non farlo sul frontend? Si noti che il backend si sta già muovendo in quella direzione, con strumenti come Kafka ampiamente adottati da grandi giocatori. Questo è in qualche modo legato anche a Event Sourcing / CQRS / DDD.

Dai un'occhiata a questi fantastici articoli degli autori di Kafka per convincerti:

Forse potremmo iniziare inviando comandi al server e ricevendo un flusso di eventi del server (tramite i websocket per esempio), invece di lanciare richieste Ajax.

Non mi sono mai trovato a mio agio con le richieste dell'Ajax. Mentre reagiamo, gli sviluppatori tendono ad essere programmatori funzionali. Penso che sia difficile ragionare sui dati locali che dovrebbero essere la tua "fonte di verità" della tua applicazione frontend, mentre la vera fonte di verità è in realtà sul database del server, e la tua fonte di "verità" locale potrebbe essere già obsoleta quando lo ricevi, e non convergeranno mai nella vera fonte del valore della verità a meno che tu non prema un po 'il pulsante di aggiornamento zoppo ... È questa ingegneria?

Tuttavia è ancora un po 'difficile progettare una cosa del genere per alcuni ovvi motivi:

  • Il tuo client mobile / browser ha risorse limitate e non può necessariamente archiviare tutti i dati localmente (quindi a volte richiede il polling con una richiesta Ajax contenuto pesante)
  • Il tuo client non dovrebbe vedere tutti i dati del sistema distribuito, quindi richiede in qualche modo di filtrare gli eventi che riceve per motivi di sicurezza

3
Potete fornire un esempio dell'uso delle promesse Q con le azioni?
Matt Foxx Duncan,

@MattFoxxDuncan non è sicuro che sia una buona idea in quanto rende il "registro eventi" non serializzabile e rende l'aggiornamento del negozio in modo asincrono sulle azioni che vengono attivate, quindi ha alcuni svantaggi Tuttavia se è ok per il tuo caso d'uso e capisci questi svantaggi è abbastanza utile e ridurre la piastra di cottura. Con Fluxxor probabilmente puoi fare qualcosa del generethis.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Sebastien Lorber,

Completamente in disaccordo sul tuo argomento AJAX. In effetti è stato molto fastidioso da leggere. Hai letto le tue osservazioni? Pensa a negozi, giochi, app che fanno soldi seri - tutti richiedono chiamate al server API e AJAX .. guarda Firebase se vuoi "serverless" o qualcosa del genere, ma AJAX è qui per dire che spero che almeno nessun altro sia d'accordo la tua logica
TheBlackBenzKid

@TheBlackBenzKid Non sto dicendo che l'Ajax scomparirà totalmente nel corso dell'anno (ed essere sicuro che sto ancora costruendo siti Web in cima alle richieste Ajax attualmente come CTO di una startup), ma sto dicendo che probabilmente scomparirà perché non è un protocollo abbastanza buono da gestire l'eventuale coerenza che richiede piuttosto lo streaming e non il polling, e l'eventuale coerenza è ciò che consente di far funzionare le app offline in modo affidabile (sì, puoi hackerare qualcosa con localstorage tu stesso, ma avrai capacità offline limitate, o la tua app è molto semplice). Il problema non è la memorizzazione nella cache, sta invalidando quella cache.
Sebastien Lorber,

@TheBlackBenzKid I modelli dietro Firebase, Meteor ecc. Non sono abbastanza buoni. Sai come questi sistemi gestiscono le scritture simultanee? last-write-win invece di coerenza causale / strategie di fusione? Potete permettervi il lavoro prioritario del vostro collega in un'app quando entrambi lavorano su connessioni inaffidabili? Si noti inoltre che questi sistemi tendono ad accoppiare molto le modellazioni locali e server. Conosci qualche app collaborativa ben nota che è significativamente complessa, funziona perfettamente offline, dichiarando di essere un utente Firebase soddisfatto? Non lo so
Sebastien Lorber,

20

Puoi chiamare i dati nei creatori dell'azione o nei negozi. L'importante è non gestire direttamente la risposta, ma creare un'azione nel callback errore / successo. Gestire la risposta direttamente nel negozio porta a un design più fragile.


9
Puoi spiegarlo più in dettaglio per favore? Di 'che devo effettuare il caricamento iniziale dei dati dal server. Nella vista controller avvio un'azione INIT e lo Store avvia la sua inizializzazione asincrona che riflette questa azione. Ora, vorrei andare con l'idea, che quando lo Store recuperava i dati, emetteva semplicemente il cambiamento, ma non avviava un'azione. Pertanto, l'emissione di una modifica dopo l'inizializzazione indica alle visualizzazioni che possono ottenere i dati dall'archivio. Perché è necessario non emettere una modifica in caso di caricamento riuscito, ma avviare un'altra azione ?! Grazie
Jim-Y,

Fisherwebdev, riguardo ai negozi che richiedono dati, così facendo, non infrangere il paradigma Flux, gli unici 2 modi in cui posso pensare di chiamare per i dati sono usando: 1. usa una classe bootstrap usando Azioni per caricare i dati 2 Viste, usando di nuovo Azioni per caricare i dati
Yotam,

4
La richiesta di dati non è la stessa cosa della ricezione di dati. @ Jim-Y: dovresti emettere il cambiamento solo dopo che i dati nel negozio sono stati effettivamente modificati. Yotam: No, la richiesta di dati nell'archivio non rompe il paradigma. I dati devono essere ricevuti solo attraverso azioni, in modo che tutti i negozi possano essere informati da eventuali nuovi dati che accedono alla domanda. Quindi possiamo chiamare i dati in un negozio, ma quando la risposta ritorna, dobbiamo creare una nuova azione invece di gestirla direttamente. Ciò mantiene l'applicazione flessibile e resiliente allo sviluppo di nuove funzionalità.
fisherwebdev,

2

Ho usato l'esempio di Binary Muse dall'esempio ajax di Fluxxor . Ecco il mio esempio molto semplice usando lo stesso approccio.

Ho un semplice negozio di prodotti con alcune azioni del prodotto e il componente di visualizzazione del controller che ha componenti secondari che rispondono tutti alle modifiche apportate al negozio di prodotti . Per esempio product-cursore , prodotto-list e prodotto di ricerca componenti.

Cliente prodotto falso

Ecco il client falso che è possibile sostituire per chiamare un endpoint effettivo che restituisce prodotti.

var ProductClient = {

  load: function(success, failure) {
    setTimeout(function() {
      var ITEMS = require('../data/product-data.js');
      success(ITEMS);
    }, 1000);
  }    
};

module.exports = ProductClient;

Negozio di prodotti

Ecco il Product Store, ovviamente questo è un negozio molto minimale.

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;

Ora le azioni del prodotto, che fanno la richiesta AJAX e in caso di successo attivano l'azione LOAD_PRODUCTS_SUCCESS che restituisce i prodotti al negozio.

Azioni sul prodotto

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;

Pertanto, la chiamata this.getFlux().actions.productActions.loadProducts()da qualsiasi componente che ascolta questo negozio carica i prodotti.

Potresti immaginare di avere azioni diverse che potrebbero rispondere alle interazioni dell'utente come addProduct(id) removeProduct(id)etc ... seguendo lo stesso schema.

Spero che questo esempio aiuti un po ', dato che l'ho trovato un po' difficile da implementare, ma sicuramente ha aiutato a mantenere i miei negozi sincronizzati al 100%.


2

Ho risposto a una domanda correlata qui: Come gestire le chiamate API annidate nel flusso

Le azioni non dovrebbero essere cose che causano un cambiamento. Dovrebbero essere come un giornale che informa l'applicazione di un cambiamento nel mondo esterno, e quindi l'applicazione risponde a quella notizia. I negozi causano cambiamenti in se stessi. Le azioni li informano e basta.

Bill Fisher, creatore di Flux https://stackoverflow.com/a/26581808/4258088

Quello che dovresti sostanzialmente fare è, indicando tramite azioni quali dati ti servono. Se il negozio viene informato dall'azione, dovrebbe decidere se deve recuperare alcuni dati.

Il negozio dovrebbe essere responsabile dell'accumulo / recupero di tutti i dati necessari. È importante notare, tuttavia, che dopo che il negozio ha richiesto i dati e ottenuto la risposta, dovrebbe innescare un'azione stessa con i dati recuperati, al contrario del negozio che gestisce / salva direttamente la risposta.

Un negozio potrebbe assomigliare a questo:

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

Ecco la mia opinione su questo: http://www.thedreaming.org/2015/03/14/react-ajax/

Spero che aiuti. :)


8
downvote secondo le linee guida. l'inserimento di risposte su siti esterni rende questo sito meno utile e consente risposte di qualità inferiore, riducendo l'utilità del sito. probabilmente anche gli URL esterni si interromperanno nel tempo. il downvote non dice nulla sull'utilità dell'articolo, che tra l'altro è molto buono :)
oligofren

2
Buon post, ma l'aggiunta di un breve riassunto dei pro / contro di ogni approccio ti farà ottenere voti positivi. Su SO, non dovremmo fare clic su un collegamento per ottenere l'essenza della tua risposta.
Cory House,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.