Où la demande ajax doit-elle être effectuée dans l'application Flux?

194

Je crée une application react.js avec une architecture de flux et j'essaie de savoir où et quand une demande de données du serveur doit être faite. Y a-t-il un exemple pour cela? (Pas l'application TODO!)

Eniz Gülek
la source

Réponses:

127

Je suis un grand partisan de la mise en place des opérations d'écriture asynchrone dans les créateurs d'actions et des opérations de lecture asynchrone dans le magasin. L'objectif est de conserver le code de modification de l'état du magasin dans des gestionnaires d'actions entièrement synchrones; cela les rend simples à raisonner et simples à tester. Afin d'empêcher plusieurs demandes simultanées vers le même point de terminaison (par exemple, double lecture), je déplacerai le traitement des demandes réelles dans un module distinct qui utilise des promesses pour empêcher les demandes multiples; par exemple:

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

Bien que les lectures dans le magasin impliquent des fonctions asynchrones, il existe une mise en garde importante selon laquelle les magasins ne se mettent pas à jour eux-mêmes dans les gestionnaires asynchrones, mais déclenchent plutôt une action et ne déclenchent une action que lorsque la réponse arrive. Les gestionnaires de cette action finissent par effectuer la modification d'état réelle.

Par exemple, un composant peut faire:

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

Le magasin aurait une méthode implémentée, peut-être quelque chose comme ceci:

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
  }
}
Michelle Tilley
la source
Avez-vous essayé de mettre des promesses dans les charges utiles d'action? Je trouve qu'il est plus facile de gérer que d'envoyer plusieurs actions
Sebastien Lorber
@SebastienLorber Le principal attrait pour Flux pour moi est de garder toutes les mises à jour d'état dans un chemin de code synchrone, et explicitement uniquement à la suite de distributions d'actions, donc j'évite l'asynchronie à l'intérieur des magasins.
Michelle Tilley
1
@Federico Je ne sais toujours pas quelle est la "meilleure" solution. J'ai expérimenté cette stratégie de chargement de données combinée au comptage du nombre de demandes asynchrones en attente. Malheureusement, il fluxest injecté dans les magasins après la construction, donc pas de bon moyen d'obtenir des actions dans la méthode d'initialisation. Vous trouverez peut-être de bonnes idées dans les bibliothèques de flux isomorphes de Yahoo; c'est quelque chose que Fluxxor v2 devrait mieux supporter. N'hésitez pas à m'envoyer un e-mail si vous souhaitez en discuter davantage.
Michelle Tilley
1
data: resultdevrait être data : data, non? il n'y a pas result. peut-être préférable de renommer le paramètre de données en charge utile ou quelque chose comme ça.
oligofren
2
J'ai trouvé ce vieux fil de discussion très utile - en particulier les commentaires de Bill Fisher et Jing Chen. Ceci est très proche de ce que propose @BinaryMuse avec la différence mineure que la répartition se produit dans le créateur d'action.
phillipwei
37

Fluxxor propose un exemple de communication asynchrone avec une API.

Ce billet de blog en parle et a été présenté sur le blog de React.


Je trouve que c'est une question très importante et difficile à laquelle il n'est pas encore clairement répondu, car la synchronisation des logiciels frontaux avec le backend est toujours difficile.

Les demandes d'API doivent-elles être effectuées dans les composants JSX? Magasins? Autre endroit?

L'exécution de requêtes dans les magasins signifie que si 2 magasins ont besoin des mêmes données pour une action donnée, ils émettront 2 requets similaires (sauf si vous introduisez des dépendances entre magasins, ce que je n'aime vraiment pas )

Dans mon cas, j'ai trouvé cela très pratique pour mettre les promesses Q comme charge utile d'actions parce que:

  • Mes actions n'ont pas besoin d'être sérialisables (je ne garde pas de journal des événements, je n'ai pas besoin de la fonction de relecture des événements de la source des événements)
  • Il supprime le besoin d'avoir différentes actions / événements (demande déclenchée / demande terminée / demande échouée) et doit les faire correspondre à l'aide d'ID de corrélation lorsque des demandes simultanées peuvent être déclenchées.
  • Il permet à plusieurs magasins d'écouter l'achèvement de la même demande, sans introduire de dépendance entre les magasins (mais il peut être préférable d'introduire une couche de mise en cache?)

Ajax est EVIL

Je pense que l'Ajax sera de moins en moins utilisé dans un futur proche car c'est très difficile à raisonner. Le droit chemin? Considérant les appareils comme faisant partie du système distribué, je ne sais pas d'où je suis tombé sur cette idée (peut-être dans cette vidéo inspirante de Chris Granger ).

Pensez-y. Maintenant, pour l'évolutivité, nous utilisons des systèmes distribués avec une cohérence éventuelle comme moteurs de stockage (parce que nous ne pouvons pas battre le théorème CAP et souvent nous voulons être disponibles). Ces systèmes ne se synchronisent pas en s'interrogeant mutuellement (sauf peut-être pour des opérations de consensus?) Mais utilisent plutôt des structures comme CRDT et les journaux d'événements pour rendre tous les membres du système distribué cohérents (les membres convergeront vers les mêmes données, avec suffisamment de temps) .

Pensez maintenant à ce qu'est un appareil mobile ou un navigateur. Il s'agit simplement d'un membre du système distribué qui peut souffrir de latence du réseau et de partitionnement du réseau. (c'est-à-dire que vous utilisez votre smartphone dans le métro)

Si nous pouvons créer des bases de données de partition réseau et de tolérance de vitesse réseau (je veux dire que nous pouvons toujours effectuer des opérations d'écriture sur un nœud isolé), nous pouvons probablement créer des logiciels frontaux (mobiles ou de bureau) inspirés de ces concepts, qui fonctionnent bien avec le mode hors ligne pris en charge. de la boîte sans indisponibilité des fonctionnalités de l'application.

Je pense que nous devrions vraiment nous inspirer du fonctionnement des bases de données pour l'architecture de nos applications frontales. Une chose à noter est que ces applications n'effectuent pas de requêtes POST et PUT et GET ajax pour s'envoyer des données, mais utilisent plutôt des journaux d'événements et CRDT pour assurer une cohérence éventuelle.

Alors pourquoi ne pas faire ça sur le frontend? Notez que le backend évolue déjà dans cette direction, avec des outils comme Kafka massivement adoptés par les grands acteurs. Cela est également lié à Event Sourcing / CQRS / DDD.

Consultez ces articles impressionnants des auteurs de Kafka pour vous convaincre:

Peut-être que nous pouvons commencer par envoyer des commandes au serveur et recevoir un flux d'événements du serveur (via des websockets par exemple), au lieu de lancer des requêtes Ajax.

Je n'ai jamais été très à l'aise avec les demandes Ajax. Au fur et à mesure que nous réagissons, les développeurs ont tendance à être des programmeurs fonctionnels. Je pense qu'il est difficile de raisonner sur les données locales qui sont censées être votre "source de vérité" de votre application frontale, alors que la véritable source de vérité est en fait sur la base de données du serveur, et votre source de vérité "locale" peut déjà être dépassée lorsque vous le recevez, et ne convergera jamais vers la vraie source de valeur de vérité à moins d'appuyer sur un bouton de rafraîchissement boiteux ... Est-ce l'ingénierie?

Cependant, il est encore un peu difficile de concevoir une telle chose pour des raisons évidentes:

  • Votre client mobile / navigateur a des ressources limitées et ne peut pas nécessairement stocker toutes les données localement (ce qui nécessite parfois une interrogation avec un contenu lourd de demande ajax)
  • Votre client ne doit pas voir toutes les données du système distribué, il nécessite donc de filtrer les événements qu'il reçoit pour des raisons de sécurité
Sébastien Lorber
la source
3
Pouvez-vous fournir un exemple d'utilisation des promesses Q avec des actions?
Matt Foxx Duncan
@MattFoxxDuncan pas sûr que ce soit une si bonne idée car il rend le "journal des événements" non sérialisable et rend la mise à jour du magasin de manière asynchrone sur les actions en cours de déclenchement, donc il a quelques inconvénients. réduire le passe-partout. Avec Fluxxor, vous pouvez probablement faire quelque chose commethis.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Sebastien Lorber
Complètement en désaccord sur votre argument AJAX. En fait, c'était très ennuyeux à lire. Avez-vous lu vos remarques? Pensez aux magasins, aux jeux, aux applications qui rapportent beaucoup d'argent - tous nécessitent des appels de serveur API et AJAX .. regardez Firebase si vous voulez "sans serveur" ou quelque chose de ce genre mais AJAX est là pour dire j'espère qu'au moins personne d'autre n'est d'accord avec votre logique
TheBlackBenzKid
@TheBlackBenzKid Je ne dis pas que Ajax disparaîtra totalement dans l'année (et assurez-vous que je construis toujours des sites Web en plus des demandes ajax actuellement en tant que CTO d'une startup), mais je dis qu'il est susceptible de disparaître parce que ce n'est pas un protocole assez bon pour gérer la cohérence éventuelle qui nécessite plutôt la diffusion en continu et non l'interrogation, et la cohérence éventuelle permet de faire fonctionner les applications hors connexion de manière fiable (oui, vous pouvez pirater quelque chose avec localstorage vous-même, mais vous aurez des capacités hors ligne limitées, ou votre application est très simple). Le problème n'est pas la mise en cache, il invalide ce cache.
Sebastien Lorber
@TheBlackBenzKid Les modèles derrière Firebase, Meteor, etc. ne sont pas assez bons. Savez-vous comment ces systèmes gèrent les écritures simultanées? last-write-win au lieu des stratégies de cohérence causale / fusion? Pouvez-vous vous permettre le travail prioritaire de votre collègue dans une application lorsque les deux travaillent sur des connexions non fiables? A noter également que ces systèmes tendent à coupler beaucoup les modélisations locales et serveur. Connaissez-vous une application collaborative bien connue qui est considérablement complexe, fonctionne parfaitement hors ligne, déclarant être un utilisateur satisfait de Firebase? Je ne le fais pas
Sebastien Lorber
20

Vous pouvez appeler des données dans les créateurs d'actions ou dans les magasins. L'important est de ne pas gérer directement la réponse, mais de créer une action dans le rappel d'erreur / succès. La gestion de la réponse directement dans le magasin conduit à une conception plus fragile.

fisherwebdev
la source
9
Pouvez-vous expliquer cela plus en détail, s'il vous plaît? Disons que je dois effectuer le chargement initial des données à partir du serveur. Dans la vue du contrôleur, je lance une action INIT et le magasin démarre son initialisation asynchrone reflétant cette action. Maintenant, j'irais avec l'idée que lorsque le magasin récupérait les données, il émettrait simplement des modifications, mais ne lancerait pas d'action. Ainsi, l'émission d'une modification après l'initialisation indique aux vues qu'elles peuvent obtenir les données du magasin. Pourquoi est-il nécessaire de ne pas émettre de changement lors du chargement réussi, mais de commencer une autre action?! Merci
Jim-Y
Fisherwebdev, à propos des magasins appelant des données, ce faisant, ne cassez-vous pas le paradigme Flux, les deux seules façons appropriées de penser à appeler des données sont en utilisant: 1. utilisez une classe bootstrap en utilisant Actions pour charger les données 2 Vues, ​​utilisant à nouveau des actions pour charger des données
Yotam
4
Demander des données n'est pas la même chose que recevoir des données. @ Jim-Y: vous ne devez émettre de changement qu'une fois que les données du magasin ont réellement changé. Yotam: Non, appeler des données dans le magasin ne rompt pas le paradigme. Les données ne doivent être reçues que par des actions, afin que tous les magasins puissent être informés par de nouvelles données entrant dans l'application. Nous pouvons donc appeler des données dans un magasin, mais lorsque la réponse revient, nous devons créer une nouvelle action au lieu de la traiter directement. Cela permet à l'application de rester flexible et résistante au développement de nouvelles fonctionnalités.
fisherwebdev
2

J'utilise l'exemple de Binary Muse de l'exemple Fluxxor ajax . Voici mon exemple très simple utilisant la même approche.

J'ai un magasin de produits simple , certaines actions de produit et le composant de vue de contrôleur qui a des sous-composants qui répondent tous aux modifications apportées au magasin de produits . Par exemple , les composants product-slider , product-list et product-search .

Client de faux produits

Voici le faux client que vous pourriez remplacer pour appeler un point de terminaison réel renvoyant des produits.

var ProductClient = {

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

module.exports = ProductClient;

Magasin de produits

Voici le magasin de produits, c'est évidemment un magasin très minimal.

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;

Maintenant, les actions de produit, qui effectuent la demande AJAX et, en cas de succès, déclenchent l'action LOAD_PRODUCTS_SUCCESS renvoyant les produits au magasin.

Actions du produit

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;

Donc, appeler à this.getFlux().actions.productActions.loadProducts()partir de n'importe quel composant écoutant ce magasin chargerait les produits.

Vous pourriez imaginer avoir différentes actions qui répondraient aux interactions des utilisateurs comme addProduct(id) removeProduct(id)etc ... en suivant le même modèle.

J'espère que cet exemple aide un peu, car j'ai trouvé cela un peu délicat à mettre en œuvre, mais certainement aidé à garder mes magasins 100% synchrones.

svnm
la source
2

J'ai répondu à une question connexe ici: comment gérer les appels d'API imbriqués dans le flux

Les actions ne sont pas censées être des choses qui provoquent un changement. Ils sont censés être comme un journal qui informe l'application d'un changement dans le monde extérieur, puis l'application répond à cette nouvelle. Les magasins provoquent des changements en eux-mêmes. Les actions les informent simplement.

Bill Fisher, créateur de Flux https://stackoverflow.com/a/26581808/4258088

Ce que vous devriez essentiellement faire, c'est indiquer, via des actions, les données dont vous avez besoin. Si le magasin est informé par l'action, il doit décider s'il doit récupérer des données.

Le magasin devrait être responsable de l'accumulation / de la récupération de toutes les données nécessaires. Il est important de noter cependant qu'après que le magasin a demandé les données et obtenu la réponse, il devrait déclencher lui-même une action avec les données extraites, contrairement au magasin qui gère / enregistre directement la réponse.

Un magasin pourrait ressembler à quelque chose comme ceci:

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
  }
}
MoeSattler
la source
0

Voici mon point de vue à ce sujet: http://www.thedreaming.org/2015/03/14/react-ajax/

J'espère que cela pourra aider. :)

Jason Walton
la source
8
downvote selon les directives. mettre des réponses sur des sites externes rend ce site moins utile et rend les réponses de moins bonne qualité, ce qui diminue l'utilité du site. les URL externes se casseront probablement aussi dans le temps. le downvote ne dit rien sur l'utilité de l'article, ce qui est d'ailleurs très bien :)
oligofren
2
Bon article, mais en ajoutant un bref résumé des avantages / inconvénients de chaque approche, vous obtiendrez des votes positifs. Sur SO, nous ne devrions pas avoir besoin de cliquer sur un lien pour obtenir l'essentiel de votre réponse.
Cory House