Les magasins de flux, ou les actions (ou les deux) doivent-ils toucher des services externes?

122

Si les magasins conservent leur propre état et ont la possibilité d'appeler des services de réseau et de stockage de données pour ce faire ... auquel cas les actions ne sont que des passeurs de messages stupides,

-OU-

... les magasins devraient-ils être des destinataires stupides de données immuables provenant des actions (et les actions être celles qui récupèrent / envoient des données entre des sources externes? Dans ce cas, le magasin agirait comme des modèles de vue et serait capable d'agréger / filtrer leurs données avant de définir leur propre état de base sur les données immuables qui leur ont été alimentées par l'action.

Il me semble que ce devrait être l’un ou l’autre (plutôt qu’un mélange des deux). Dans l'affirmative, pourquoi l'un est-il préféré / recommandé par rapport à l'autre?

plaxdan
la source
2
Ce message pourrait aider code-experience.com
Markus-ipse
Pour ceux qui évaluent les différentes implémentations du modèle de flux, je recommande vivement de jeter un œil à Redux github.com/rackt/redux Les magasins sont implémentés comme des fonctions pures qui prennent l'état actuel et émettent une nouvelle version de cet état. Puisqu'il s'agit de fonctions pures, la question de savoir s'ils peuvent ou non appeler des services de réseau et de stockage vous est retirée: ils ne le peuvent pas.
plaxdan

Réponses:

151

J'ai vu le modèle de flux implémenté dans les deux sens, et après avoir fait les deux moi-même (initialement avec l'ancienne approche), je pense que les magasins devraient être des destinataires stupides des données des actions, et que le traitement asynchrone des écritures devrait vivre dans le créateurs d'action. (Les lectures asynchrones peuvent être gérées différemment .) D'après mon expérience, cela présente quelques avantages, par ordre d'importance:

  1. Vos magasins deviennent complètement synchrones. Cela rend la logique de votre magasin beaucoup plus facile à suivre et très facile à tester: instanciez simplement un magasin avec un état donné, envoyez-lui une action et vérifiez si l'état a changé comme prévu. En outre, l'un des concepts de base du flux est d'empêcher les envois en cascade et d'empêcher plusieurs envois à la fois; c'est très difficile à faire lorsque vos magasins effectuent un traitement asynchrone.

  2. Toutes les expéditions d'actions proviennent des créateurs d'action. Si vous gérez des opérations asynchrones dans vos magasins et que vous souhaitez garder les gestionnaires d'actions de vos magasins synchrones (et vous devriez le faire pour obtenir les garanties d'envoi unique de flux), vos magasins devront déclencher des actions SUCCESS et FAIL supplémentaires en réponse à des actions asynchrones. En traitement. Placer ces dépêches dans les créateurs d'action aide à la place à séparer les tâches des créateurs d'action et des magasins; De plus, vous n'avez pas à fouiller dans la logique de votre magasin pour déterminer d'où les actions sont expédiées. Une action asynchrone typique dans ce cas peut ressembler à quelque chose comme ceci (changez la syntaxe des dispatchappels en fonction de la saveur du flux que vous utilisez):

    someActionCreator: function(userId) {
      // Dispatch an action now so that stores that want
      // to optimistically update their state can do so.
      dispatch("SOME_ACTION", {userId: userId});
    
      // This example uses promises, but you can use Node-style
      // callbacks or whatever you want for error handling.
      SomeDataAccessLayer.doSomething(userId)
      .then(function(newData) {
        // Stores that optimistically updated may not do anything
        // with a "SUCCESS" action, but you might e.g. stop showing
        // a loading indicator, etc.
        dispatch("SOME_ACTION_SUCCESS", {userId: userId, newData: newData});
      }, function(error) {
        // Stores can roll back by watching for the error case.
        dispatch("SOME_ACTION_FAIL", {userId: userId, error: error});
      });
    }
    

    La logique qui pourrait autrement être dupliquée à travers diverses actions doit être extraite dans un module séparé; dans cet exemple, ce module serait SomeDataAccessLayer, qui gère la requête Ajax réelle.

  3. Vous avez besoin de moins de créateurs d'action. C'est moins grave, mais agréable à avoir. Comme mentionné dans # 2, si vos magasins ont une gestion de répartition des actions synchrones (et ils devraient), vous devrez déclencher des actions supplémentaires pour gérer les résultats des opérations asynchrones. Faire les distributions dans les créateurs d'action signifie qu'un seul créateur d'action peut distribuer les trois types d'action en gérant le résultat de l'accès asynchrone aux données lui-même.

Michelle Tilley
la source
15
Je pense que l'origine de l'appel de l'API Web (créateur d'action contre magasin) est moins importante que le fait que le rappel de succès / erreur devrait créer une action. Ainsi, le flux de données est alors toujours: action -> dispatcher -> stores -> vues.
fisherwebdev
1
Est-ce que mettre la logique de demande réelle dans un module API serait meilleur / plus facile à tester? Ainsi, votre module API peut simplement renvoyer une promesse que vous expédiez. Le créateur de l'action envoie simplement en fonction de la résolution / de l'échec après l'envoi d'une action initiale «en attente». La question qui reste est de savoir comment le composant écoute ces `` événements '' car je ne suis pas sûr que l'état de la demande doive correspondre à l'état de stockage.
backdesk
@backdesk C'est exactement ce que je fais dans l'exemple ci-dessus: envoyer une action initiale en attente ( "SOME_ACTION"), utiliser une API pour faire une requête ( SomeDataAccessLayer.doSomething(userId)) qui renvoie une promesse, et dans les deux .thenfonctions, envoyer des actions supplémentaires. L'état de la demande peut (plus ou moins) mapper sur l'état de stockage si l'application a besoin de connaître l'état de l'état. Comment ces cartes dépendent de l'application (par exemple, peut-être que chaque commentaire a un état d'erreur individuel, à la Facebook, ou peut-être qu'il y a un composant d'erreur global)
Michelle Tilley
@MichelleTilley "l'un des concepts de base de flux est d'empêcher les expéditions en cascade et d'empêcher plusieurs expéditions à la fois; c'est très difficile à faire lorsque vos magasins effectuent un traitement asynchrone." C'est un point clé pour moi. Bien dit.
51

J'ai tweeté cette question aux développeurs de Facebook et la réponse de Bill Fisher a été:

En répondant à l'interaction d'un utilisateur avec l'interface utilisateur, je ferais l'appel asynchrone dans les méthodes du créateur d'action.

Mais lorsque vous avez un ticker ou un autre pilote non humain, un appel du magasin fonctionne mieux.

L'important est de créer une action dans le rappel d'erreur / réussite afin que les données proviennent toujours d'actions

Markus-ipse
la source
Bien que cela ait du sens, une idée pourquoi a call from store works better when action triggers from non-human driver ?
SharpCoder
@SharpCoder Je suppose que si vous avez un live-ticker ou quelque chose de similaire, vous n'avez pas vraiment besoin de déclencher une action et lorsque vous faites cela à partir du magasin, vous devez probablement écrire moins de code, car le magasin peut accéder instantanément à l'état & émettre un changement.
Florian Wendelborn
8

Les magasins doivent tout faire, y compris récupérer les données et signaler aux composants que les données du magasin ont été mises à jour. Pourquoi? Parce que les actions peuvent alors être légères, jetables et remplaçables sans influencer les comportements importants. Tous les comportements et fonctionnalités importants se produisent dans le magasin. Cela évite également la duplication de comportements qui seraient autrement copiés dans deux actions très similaires mais différentes. Les magasins sont votre source unique de (gestion de) la vérité.

Dans chaque implémentation de Flux que j'ai vue, les Actions sont essentiellement des chaînes d'événements transformées en objets, comme traditionnellement vous auriez un événement nommé "anchor: clicked" mais dans Flux, il serait défini comme AnchorActions.Clicked. Ils sont même si "stupides" que la plupart des implémentations ont des objets Dispatcher séparés pour envoyer réellement les événements aux magasins qui écoutent.

Personnellement, j'aime l'implémentation de Flux par Reflux où il n'y a pas d'objets Dispatcher séparés et les objets Action effectuent le dispatching eux-mêmes.


edit: Flux de Facebook récupère en fait des "créateurs d'action" afin qu'ils utilisent des actions intelligentes. Ils préparent également la charge utile à l'aide des magasins:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/actions/ChatMessageActionCreators.js#L27 (lignes 27 et 28)

Le rappel à la fin déclencherait alors une nouvelle action cette fois avec les données extraites comme charge utile:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/ChatWebAPIUtils.js#L51

Alors je suppose que c'est la meilleure solution.

Rygu
la source
Quelle est cette implémentation Reflux? Je n'en ai pas entendu parler. Votre réponse est intéressante. Vous voulez dire que l'implémentation de votre boutique doit avoir la logique de faire des appels d'API, etc.? Je pensais que les magasins devraient simplement recevoir des données et simplement mettre à jour leurs valeurs. Ils filtrent sur des actions spécifiques et mettent à jour certains attributs de leurs magasins.
Jeremy D
Reflux est une légère variation du Flux de Facebook: github.com/spoike/refluxjs Les magasins gèrent tout le domaine «Modèle» de votre application, vs Actions / Dispatchers qui ne font que rassembler et coller les choses ensemble.
Rygu
1
J'y ai donc réfléchi un peu plus et j'ai (presque) répondu à ma propre question. Je l'aurais ajouté comme réponse ici (pour que d'autres votent) mais apparemment, je suis trop pauvre en karma à stackoverflow pour pouvoir encore publier une réponse. Alors, voici un lien: groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
plaxdan
Merci pour le lien du groupe Google, cela semble vraiment instructif. Je suis aussi plus fan de tout ce qui passe par le répartiteur, et d'une logique très simple dans le magasin, en gros, mettre à jour leurs données, c'est tout. @Rygu je vais vérifier le reflux.
Jeremy D
J'ai modifié ma réponse avec une autre vue. Il semble que les deux solutions soient possibles. Je choisirais presque certainement la solution de Facebook par rapport aux autres.
Rygu
3

Je vais fournir un argument en faveur des actions «stupides».

En plaçant la responsabilité de la collecte des données de vue dans vos actions, vous associez vos actions aux exigences de données de vos vues.

En revanche, les actions génériques, qui décrivent de manière déclarative l' intention de l'utilisateur, ou une transition d'état dans votre application, permettent à tout magasin qui répond à cette action de transformer l'intention, en un état spécialement conçu pour les vues qui y sont abonnées.

Cela se prête à des magasins plus nombreux, mais plus petits et plus spécialisés. Je défends ce style parce que

  • cela vous donne plus de flexibilité dans la façon dont les vues consomment les données du Store
  • Les stores "intelligents", spécialisés pour les vues qui les consomment, seront plus petits et moins couplés pour les applications complexes, que les actions "intelligentes", dont dépendent potentiellement de nombreuses vues

Le but d'un magasin est de fournir des données aux vues. Le nom «Action» me suggère que son but est de décrire un changement dans ma candidature.

Supposons que vous deviez ajouter un widget à une vue de tableau de bord existante, qui montre de nouvelles données agrégées sophistiquées que votre équipe backend vient de déployer.

Avec les actions «intelligentes», vous devrez peut-être modifier votre action «rafraîchir le tableau de bord» pour utiliser la nouvelle API. Cependant, «Actualiser le tableau de bord» dans un sens abstrait n'a pas changé. Les exigences de données de vos vues sont ce qui a changé.

Avec des actions "stupides", vous pouvez ajouter un nouveau magasin pour le nouveau widget à consommer, et le configurer de sorte que lorsqu'il reçoit le type d'action "refresh-dashboard", il envoie une demande pour les nouvelles données et les expose à le nouveau widget une fois qu'il est prêt. Il est logique pour moi que lorsque la couche de vue a besoin de plus de données ou de données différentes, les choses que je change sont les sources de ces données: les magasins.

Carlos Lalimarmo
la source
2

La démo flux- react -router-demo de gaeron a une belle variante utilitaire de l'approche «correcte».

Un ActionCreator génère une promesse à partir d'un service d'API externe, puis transmet la promesse et trois constantes d'action à une dispatchAsyncfonction dans un proxy / Dispatcher étendu. dispatchAsyncenverra toujours la première action, par exemple «GET_EXTERNAL_DATA» et une fois la promesse renvoyée, elle enverra soit «GET_EXTERNAL_DATA_SUCCESS» ou «GET_EXTERNAL_DATA_ERROR».

William Myers
la source
1

Si vous voulez un jour avoir un environnement de développement comparable à ce que vous voyez dans la célèbre vidéo Inventing on Principle de Bret Victor , vous devriez plutôt utiliser des magasins stupides qui ne sont qu'une projection d'actions / d'événements à l'intérieur d'une structure de données, sans aucun effet secondaire. Cela aiderait également si vos magasins étaient réellement membres de la même structure de données immuables globale, comme dans Redux .

Plus d'explications ici: https://stackoverflow.com/a/31388262/82609

Sébastien Lorber
la source