Redux: Approche alternative aux actions asynchrones

Créé le 27 déc. 2015  ·  44Commentaires  ·  Source: reduxjs/redux

J'ai exploré une alternative à la manière dont les actions asynchrones sont effectuées en redux, et j'apprécierais tous les commentaires que d'autres pourraient avoir sur ce que j'ai fait.

Pour illustrer mon approche, j'ai modifié l'exemple asynchrone dans mon clone de redux : https://github.com/winstonewert/redux/tree/master/examples/async

En règle générale, les actions externes sont effectuées en rendant les créateurs d'actions asynchrones. Dans le cas de l'exemple asynchrone, le créateur de l'action fetchPosts envoie une action REQUEST_POSTS pour indiquer le début de la requête, suivie d'un RECEIVE_POSTS une fois que les publications sont revenues de l'api.

Dans mon exemple, tous les créateurs d'action sont synchrones. Au lieu de cela, il existe une fonction qui renvoie la liste des actions asynchrones qui devraient actuellement avoir lieu en fonction de l'état. Voir mon exemple ici : https://github.com/rackt/redux/compare/master...winstonewert :master#diff-8a94dc7aa7bdc6e5390c9216a69761f8R12

La fonction doReactions s'abonne au magasin et garantit que l'état réel des demandes en cours correspond à l'état renvoyé par l'état doReactions en démarrant ou en annulant les demandes.

Alors, quelle est la différence ?

1) La fonction de réaction est une fonction pure de l'état. Cela facilite le test.
2) La logique réelle des requêtes à faire est plus simple. Voir la fonction de quelques lignes dans mon exemple, par rapport aux divers éléments de logique répandus auparavant dans les conteneurs et les créateurs d'action.
3) Facilite l'annulation des demandes.

Des pensées?

discussion feedback wanted

Commentaire le plus utile

J'ai moi aussi beaucoup réfléchi à d'autres moyens de gérer les effets secondaires dans le redux et j'espère ne pas détourner votre fil alors que je me débarrasse de certains des problèmes que je vois avec certaines approches actuelles et pourquoi et comment je pense que c'est grand pas dans la bonne direction malgré son apparente simplicité.

Le problème des effets secondaires dans les créateurs d'action

Dans les langages fonctionnels purs, les effets secondaires sont toujours remontés à la périphérie de l'application et renvoyés au runtime pour exécution. Dans Elm, les réducteurs renvoient un tuple contenant le nouvel état et tous les effets qui doivent être exécutés. Les méthodes avec cette signature ne sont cependant pas encore composables avec d'autres réducteurs redux.

L'endroit évident (mais peut-être pas le meilleur) pour effectuer des effets secondaires dans le redux est devenu les créateurs d'action et plusieurs options de middleware différentes ont été développées pour prendre en charge ce modèle. Cependant, je pense un peu que les approches middleware actuelles sont plus une solution de contournement pour ne pas être en mesure de renvoyer les effets secondaires en tant que concept de première classe des réducteurs.

Alors que les gens construisent toujours des choses géniales avec Redux et que c'est un grand pas en avant et bien plus simple et plus pragmatique que la plupart des alternatives, il y a quelques problèmes que je vois avec les effets secondaires dans les créateurs d'action :

  • L'état implicite est masqué
  • Duplication de la logique métier
  • Les hypothèses et/ou dépendances contextuelles réduisent la réutilisabilité
  • Les créateurs d'action avec des effets secondaires sont difficiles à tester
  • Ne peut pas être optimisé ou groupé

L'état implicite est masqué

Dans le compteur, l'application incrementAsync crée un délai d'attente et ce n'est qu'à son achèvement que l'état de l'application est mis à jour. Si vous vouliez, par exemple, afficher un indicateur visuel qu'une opération d'incrémentation est en cours, la vue ne peut pas le déduire de l'état de l'application. Cet état est implicite et caché.

Bien que parfois élégant, je ne suis pas si sûr de la proposition d'utiliser des générateurs comme orchestrateurs de créateurs d'action puisque l'état implicite est caché et ne peut pas être facilement sérialisé.

En utilisant redux-thunk ou similaire, vous pouvez envoyer plusieurs messages au réducteur pour l'informer du début et de la fin de l'opération d'incrémentation, mais cela crée un autre ensemble de problèmes.

Rembobiner l'état jusqu'à un point où l'opération d'incrémentation est marquée comme en cours une fois l'effet terminé ne régénérera pas réellement l'effet secondaire et restera donc en cours indéfiniment.

Votre proposition semble résoudre ce problème. Étant donné que les effets secondaires sont créés à partir de l'état, l'intention doit être exprimée avec l'état résultant sous une forme ou une autre. laisser l'État dans les limbes.

Duplication de la logique métier

Il est naturel que les actions génèrent un effet secondaire uniquement lorsque l'application est dans un état spécifique. En redux, si un créateur d'action nécessite un état, il doit s'agir d'une fonction simple et pure ou explicitement fournie avec l'état.

Comme exemple simple, disons que nous commençons par l'exemple d'application de compteur et que nous voulons changer le compteur en une couleur de police aléatoire chaque fois que le compteur est un multiple de 5.

Étant donné que la génération de nombres aléatoires est impure, l'endroit suggéré pour mettre ce comportement est dans le créateur d'action. Cependant, il existe plusieurs actions différentes qui peuvent changer la valeur du compteur, incrément, décrément, incrementAsync, incrementIfOdd (qui n'a pas besoin d'être modifié dans ce cas).

l'incrément et la décrémentation ne nécessitaient auparavant aucun état car ils étaient auparavant gérés dans le réducteur et avaient donc accès à la valeur actuelle, mais comme un réducteur ne peut pas avoir ou renvoyer d'effets secondaires (génération de nombres aléatoires), ces fonctions deviennent maintenant des créateurs d'actions impurs qui ont besoin pour connaître la valeur actuelle du compteur pour déterminer s'il est nécessaire de sélectionner une nouvelle couleur de police aléatoire et cette logique doit être dupliquée dans tous les créateurs d'action de compteur.

Une alternative possible à la fourniture explicite de l'état actuel serait d'utiliser redux-thunk et de renvoyer un rappel pour accéder à l'état actuel. Cela vous permet d'éviter de modifier tous les endroits où les actions sont créées pour fournir la valeur actuelle, mais cela nécessite maintenant que le créateur de l'action sache où dans l'état global de l'application la valeur est stockée et cela limite la possibilité de réutiliser le même compteur plusieurs fois dans la même application ou dans des applications différentes où l'état peut être structuré différemment.

Les hypothèses et/ou dépendances contextuelles réduisent la réutilisabilité

En revisitant à nouveau le contre-exemple, vous remarquerez qu'il n'y a qu'une seule contre-instance. Bien qu'il soit trivial d'avoir de nombreux compteurs sur la page qui affichent/mettent à jour le même état, des modifications supplémentaires du compteur sont nécessaires si vous souhaitez que chaque compteur utilise un état différent.

Cela a déjà été discuté. Comment créer une liste générique en tant que réducteur et amplificateur de composant ?

Si le compteur n'utilisait que des types d'action simples, il serait relativement trivial d'appliquer l' architecture elm .

Dans ce cas, le parent enveloppe simplement les créateurs d'action ou le répartiteur pour augmenter le message avec tout contexte nécessaire, il peut ensuite appeler le réducteur directement avec l'état localisé.

Alors que l' exemple React Elmish semble impressionnant, il manque notamment dans l'exemple les deux créateurs d'action problématiques, incrementIfOdd et incrementAsync.

incrementIfOdd dépend du middleware pour déterminer l'état actuel et doit donc connaître son emplacement dans l'état de l'application.

incrementAsync distribue finalement directement une action d'incrémentation qui n'est pas exposée au composant parent et ne peut donc pas être enveloppée avec un contexte supplémentaire.

Bien que votre proposition ne résolve pas directement ce problème, si incrementAsync a été implémenté en tant qu'action simple qui a changé l'état en {counter: 0, incrementAfterDelay: 1000} pour déclencher l'effet secondaire dans un écouteur de magasin, alors incrementAsync devient un simple message. incrementIfOdd est pur, il peut donc être implémenté dans le réducteur ou l'état peut être fourni explicitement.... Ainsi, il devient possible d'appliquer à nouveau l'architecture elm si vous le souhaitez.

Les créateurs d'action avec des effets secondaires sont difficiles à tester

Je pense que c'est assez évident que les effets secondaires vont être plus difficiles à tester. Une fois que vos effets secondaires dépendent de l'état actuel et de la logique métier, ils deviennent non seulement plus difficiles, mais aussi plus importants à tester.

Votre proposition permet de créer facilement un test selon lequel une transition d'état créera un état contenant les réactions souhaitées sans réellement exécuter aucune d'entre elles. Les réactions sont également plus faciles à tester car elles ne nécessitent aucun état conditionnel ou logique métier.

Ne peut pas être optimisé ou groupé

Un récent article de blog de John A De Goes a abordé le problème des types de données opaques tels que IO ou Task pour exprimer les effets. En utilisant des descriptions déclaratives des effets secondaires plutôt que des types opaques, vous avez la possibilité d'optimiser ou de combiner les effets plus tard.

Une architecture moderne pour FP

Les thunks, les promesses et les générateurs sont opaques et donc les optimisations telles que le traitement par lots et/ou la suppression des appels d'API en double doivent être gérées explicitement avec des fonctions similaires à fetchPostsIfNeeded .

Votre proposition élimine fetchPostsIfNeeded et il semble tout à fait faisable d'implémenter une fonction reactions qui pourrait optimiser plusieurs requêtes et/ou utiliser différents ensembles d'API selon les besoins lorsque plus ou moins de données ont été demandées.

Ma mise en oeuvre

J'ai récemment créé un fork de redux qui permet de créer des réducteurs qui renvoient uniquement le nouvel état comme ils le font maintenant ou un objet spécial avec des effets contenant le nouvel état et une description de tous les effets à exécuter après le réducteur.

Je ne savais pas trop comment faire cela sans fork redux car il était nécessaire de modifier compose et combineReducers pour lever les effets sur les réducteurs existants afin de maintenir la compatibilité avec le code réducteur existant.

Votre proposition est cependant assez agréable dans la mesure où elle ne nécessite pas de modification du redux. De plus, je pense que votre solution résout mieux le problème d'état caché implicite et est probablement plus facile à combiner ou à optimiser les réactions résultantes.

Résumé

Tout comme React est "juste l'interface utilisateur" et n'est pas très prescriptif sur la façon dont on stocke ou met à jour l'état de l'application, Redux est principalement "juste le magasin" et n'est pas très prescriptif sur la façon dont on gère les effets secondaires.

Je n'ai jamais reproché à personne d'être pragmatique et de faire avancer les choses et les nombreux contributeurs au redux et au middleware ont permis aux gens de créer des trucs vraiment cool plus rapidement et mieux qu'il n'était possible auparavant. Ce n'est que grâce à leurs contributions que nous en sommes arrivés là. Un merci spécial à tous ceux qui ont contribué.

Redux est génial. Ce ne sont pas des problèmes nécessaires avec Redux lui-même, mais, espérons-le, des critiques constructives des modèles architecturaux actuels et des motivations et avantages potentiels de l'exécution des effets après plutôt qu'avant les modifications d'état.

Tous les 44 commentaires

Approche intéressante ! Il semble que cela devrait idéalement être découplé de la nature de l'asynchronie, comme la méthode utilisée pour exécuter le XHR, ou même qu'une requête Web est la source de l'asynchronie en premier lieu.

J'ai moi aussi beaucoup réfléchi à d'autres moyens de gérer les effets secondaires dans le redux et j'espère ne pas détourner votre fil alors que je me débarrasse de certains des problèmes que je vois avec certaines approches actuelles et pourquoi et comment je pense que c'est grand pas dans la bonne direction malgré son apparente simplicité.

Le problème des effets secondaires dans les créateurs d'action

Dans les langages fonctionnels purs, les effets secondaires sont toujours remontés à la périphérie de l'application et renvoyés au runtime pour exécution. Dans Elm, les réducteurs renvoient un tuple contenant le nouvel état et tous les effets qui doivent être exécutés. Les méthodes avec cette signature ne sont cependant pas encore composables avec d'autres réducteurs redux.

L'endroit évident (mais peut-être pas le meilleur) pour effectuer des effets secondaires dans le redux est devenu les créateurs d'action et plusieurs options de middleware différentes ont été développées pour prendre en charge ce modèle. Cependant, je pense un peu que les approches middleware actuelles sont plus une solution de contournement pour ne pas être en mesure de renvoyer les effets secondaires en tant que concept de première classe des réducteurs.

Alors que les gens construisent toujours des choses géniales avec Redux et que c'est un grand pas en avant et bien plus simple et plus pragmatique que la plupart des alternatives, il y a quelques problèmes que je vois avec les effets secondaires dans les créateurs d'action :

  • L'état implicite est masqué
  • Duplication de la logique métier
  • Les hypothèses et/ou dépendances contextuelles réduisent la réutilisabilité
  • Les créateurs d'action avec des effets secondaires sont difficiles à tester
  • Ne peut pas être optimisé ou groupé

L'état implicite est masqué

Dans le compteur, l'application incrementAsync crée un délai d'attente et ce n'est qu'à son achèvement que l'état de l'application est mis à jour. Si vous vouliez, par exemple, afficher un indicateur visuel qu'une opération d'incrémentation est en cours, la vue ne peut pas le déduire de l'état de l'application. Cet état est implicite et caché.

Bien que parfois élégant, je ne suis pas si sûr de la proposition d'utiliser des générateurs comme orchestrateurs de créateurs d'action puisque l'état implicite est caché et ne peut pas être facilement sérialisé.

En utilisant redux-thunk ou similaire, vous pouvez envoyer plusieurs messages au réducteur pour l'informer du début et de la fin de l'opération d'incrémentation, mais cela crée un autre ensemble de problèmes.

Rembobiner l'état jusqu'à un point où l'opération d'incrémentation est marquée comme en cours une fois l'effet terminé ne régénérera pas réellement l'effet secondaire et restera donc en cours indéfiniment.

Votre proposition semble résoudre ce problème. Étant donné que les effets secondaires sont créés à partir de l'état, l'intention doit être exprimée avec l'état résultant sous une forme ou une autre. laisser l'État dans les limbes.

Duplication de la logique métier

Il est naturel que les actions génèrent un effet secondaire uniquement lorsque l'application est dans un état spécifique. En redux, si un créateur d'action nécessite un état, il doit s'agir d'une fonction simple et pure ou explicitement fournie avec l'état.

Comme exemple simple, disons que nous commençons par l'exemple d'application de compteur et que nous voulons changer le compteur en une couleur de police aléatoire chaque fois que le compteur est un multiple de 5.

Étant donné que la génération de nombres aléatoires est impure, l'endroit suggéré pour mettre ce comportement est dans le créateur d'action. Cependant, il existe plusieurs actions différentes qui peuvent changer la valeur du compteur, incrément, décrément, incrementAsync, incrementIfOdd (qui n'a pas besoin d'être modifié dans ce cas).

l'incrément et la décrémentation ne nécessitaient auparavant aucun état car ils étaient auparavant gérés dans le réducteur et avaient donc accès à la valeur actuelle, mais comme un réducteur ne peut pas avoir ou renvoyer d'effets secondaires (génération de nombres aléatoires), ces fonctions deviennent maintenant des créateurs d'actions impurs qui ont besoin pour connaître la valeur actuelle du compteur pour déterminer s'il est nécessaire de sélectionner une nouvelle couleur de police aléatoire et cette logique doit être dupliquée dans tous les créateurs d'action de compteur.

Une alternative possible à la fourniture explicite de l'état actuel serait d'utiliser redux-thunk et de renvoyer un rappel pour accéder à l'état actuel. Cela vous permet d'éviter de modifier tous les endroits où les actions sont créées pour fournir la valeur actuelle, mais cela nécessite maintenant que le créateur de l'action sache où dans l'état global de l'application la valeur est stockée et cela limite la possibilité de réutiliser le même compteur plusieurs fois dans la même application ou dans des applications différentes où l'état peut être structuré différemment.

Les hypothèses et/ou dépendances contextuelles réduisent la réutilisabilité

En revisitant à nouveau le contre-exemple, vous remarquerez qu'il n'y a qu'une seule contre-instance. Bien qu'il soit trivial d'avoir de nombreux compteurs sur la page qui affichent/mettent à jour le même état, des modifications supplémentaires du compteur sont nécessaires si vous souhaitez que chaque compteur utilise un état différent.

Cela a déjà été discuté. Comment créer une liste générique en tant que réducteur et amplificateur de composant ?

Si le compteur n'utilisait que des types d'action simples, il serait relativement trivial d'appliquer l' architecture elm .

Dans ce cas, le parent enveloppe simplement les créateurs d'action ou le répartiteur pour augmenter le message avec tout contexte nécessaire, il peut ensuite appeler le réducteur directement avec l'état localisé.

Alors que l' exemple React Elmish semble impressionnant, il manque notamment dans l'exemple les deux créateurs d'action problématiques, incrementIfOdd et incrementAsync.

incrementIfOdd dépend du middleware pour déterminer l'état actuel et doit donc connaître son emplacement dans l'état de l'application.

incrementAsync distribue finalement directement une action d'incrémentation qui n'est pas exposée au composant parent et ne peut donc pas être enveloppée avec un contexte supplémentaire.

Bien que votre proposition ne résolve pas directement ce problème, si incrementAsync a été implémenté en tant qu'action simple qui a changé l'état en {counter: 0, incrementAfterDelay: 1000} pour déclencher l'effet secondaire dans un écouteur de magasin, alors incrementAsync devient un simple message. incrementIfOdd est pur, il peut donc être implémenté dans le réducteur ou l'état peut être fourni explicitement.... Ainsi, il devient possible d'appliquer à nouveau l'architecture elm si vous le souhaitez.

Les créateurs d'action avec des effets secondaires sont difficiles à tester

Je pense que c'est assez évident que les effets secondaires vont être plus difficiles à tester. Une fois que vos effets secondaires dépendent de l'état actuel et de la logique métier, ils deviennent non seulement plus difficiles, mais aussi plus importants à tester.

Votre proposition permet de créer facilement un test selon lequel une transition d'état créera un état contenant les réactions souhaitées sans réellement exécuter aucune d'entre elles. Les réactions sont également plus faciles à tester car elles ne nécessitent aucun état conditionnel ou logique métier.

Ne peut pas être optimisé ou groupé

Un récent article de blog de John A De Goes a abordé le problème des types de données opaques tels que IO ou Task pour exprimer les effets. En utilisant des descriptions déclaratives des effets secondaires plutôt que des types opaques, vous avez la possibilité d'optimiser ou de combiner les effets plus tard.

Une architecture moderne pour FP

Les thunks, les promesses et les générateurs sont opaques et donc les optimisations telles que le traitement par lots et/ou la suppression des appels d'API en double doivent être gérées explicitement avec des fonctions similaires à fetchPostsIfNeeded .

Votre proposition élimine fetchPostsIfNeeded et il semble tout à fait faisable d'implémenter une fonction reactions qui pourrait optimiser plusieurs requêtes et/ou utiliser différents ensembles d'API selon les besoins lorsque plus ou moins de données ont été demandées.

Ma mise en oeuvre

J'ai récemment créé un fork de redux qui permet de créer des réducteurs qui renvoient uniquement le nouvel état comme ils le font maintenant ou un objet spécial avec des effets contenant le nouvel état et une description de tous les effets à exécuter après le réducteur.

Je ne savais pas trop comment faire cela sans fork redux car il était nécessaire de modifier compose et combineReducers pour lever les effets sur les réducteurs existants afin de maintenir la compatibilité avec le code réducteur existant.

Votre proposition est cependant assez agréable dans la mesure où elle ne nécessite pas de modification du redux. De plus, je pense que votre solution résout mieux le problème d'état caché implicite et est probablement plus facile à combiner ou à optimiser les réactions résultantes.

Résumé

Tout comme React est "juste l'interface utilisateur" et n'est pas très prescriptif sur la façon dont on stocke ou met à jour l'état de l'application, Redux est principalement "juste le magasin" et n'est pas très prescriptif sur la façon dont on gère les effets secondaires.

Je n'ai jamais reproché à personne d'être pragmatique et de faire avancer les choses et les nombreux contributeurs au redux et au middleware ont permis aux gens de créer des trucs vraiment cool plus rapidement et mieux qu'il n'était possible auparavant. Ce n'est que grâce à leurs contributions que nous en sommes arrivés là. Un merci spécial à tous ceux qui ont contribué.

Redux est génial. Ce ne sont pas des problèmes nécessaires avec Redux lui-même, mais, espérons-le, des critiques constructives des modèles architecturaux actuels et des motivations et avantages potentiels de l'exécution des effets après plutôt qu'avant les modifications d'état.

J'essaie de comprendre la différence entre cette approche et redux-saga. Je suis intéressé par l'affirmation selon laquelle il cache implicitement l'état dans les générateurs, car au début, il semble qu'il fasse la même chose. Mais je suppose que cela peut dépendre de la façon dont io.take est implémenté. Si la saga ne traitera une action que si elle est actuellement bloquée à ce yield , alors je vois vraiment ce que vous voulez dire. Mais si redux-saga met en file d'attente des actions telles que io.take renverra des actions passées, il semble qu'il fasse la même chose. Dans tous les cas, vous disposez d'une logique qui peut dispatch actions de manière asynchrone, déclenchée en écoutant le flux d'action.

C'est quand même un concept intéressant. Conceptualiser Redux comme un flux d'action, à partir duquel les transitions d'état et les effets sont déclenchés. Cela me semble être un point de vue alternatif à celui de le considérer uniquement comme un processeur d'état.

Dans le modèle d'approvisionnement d'événements, je pense que cela se résume en quelque sorte à savoir si les actions Redux sont des "commandes" (demandes contingentes pour effectuer une action) ou des "événements" (transitions d'état atomiques, reflétées dans une vue plate). Je suppose que nous avons un outil suffisamment flexible pour être envisagé dans les deux sens.

Moi aussi, je suis un peu insatisfait du statu quo des "créateurs d'action intelligents", mais je l'aborde d'une manière différente, dans laquelle Redux est davantage le magasin d'événements - où les actions sont l'un des nombreux effets possibles qui peut être déclenché par une couche "contrôleur" externe. J'ai pris en compte le code qui suivait cette approche dans react-redux-controller , bien que j'aie une idée en tête d'un moyen potentiellement plus léger d'y parvenir. Cependant, il faudrait que react-redux ait un crochet qu'il n'a pas actuellement, et quelques hijinks d'emballage de magasin que je n'ai pas tout à fait compris.

Hijinks de magasin décrits https://github.com/rackt/redux/issues/1200

J'essaye de comprendre la différence entre cette approche et redux-saga

Je n'ai vu redux-saga qu'après avoir proposé mon approche, mais il y a certainement des similitudes. Mais j'ai quand même quelques différences :

  1. Mon approche n'a pas accès au flux d'action, seulement à l'état. redux-saga peut démarrer le processus simplement parce qu'il y a eu une action. Mon approche nécessite qu'un réducteur modifie l'état qui déclenche la fonction de réaction pour demander l'action.
  2. Mon approche nécessite que tous les états existent dans l'état de redux. Redux-saga a l'état supplémentaire qui réside dans le générateur de saga (sur quelle ligne il se trouve, les valeurs des variables locales).
  3. Mon approche isole la partie asynchrone. La logique réelle de la réaction peut être testée sans traiter de la fonctionnalité asynchrone. La saga les rassemble.
  4. La saga rassemble différents morceaux d'une même logique. Mon approche vous oblige à diviser une saga en parties qui appartiennent à la mise en œuvre du réducteur, des réactions et du type de réaction.

Fondamentalement, mon approche met l'accent sur les fonctions pures et le maintien de tout dans l'état redux. L'approche redux-saga met l'accent sur le fait d'être plus expressif. Je pense qu'il y a du pour et du contre, mais j'aime mieux le mien. Mais je suis partial.

Cela semble vraiment prometteur. Je pense qu'il serait plus convaincant de voir un exemple qui distingue la machinerie de réaction de la logique du domaine.

Votre proposition élimine fetchPostsIfNeeded et il semble tout à fait faisable de mettre en œuvre une fonction de réactions qui pourrait optimiser plusieurs demandes et/ou utiliser différents ensembles d'API selon les besoins lorsque plus ou moins de données ont été demandées.

Dans l'état actuel des choses, vous ne pourriez pas vraiment faire cela dans la fonction de réactions. La logique là-bas aurait besoin de savoir quelles actions sont déjà commencées (nous ne pouvons rien y ajouter de plus), mais la fonction de réactions n'a pas les informations. La machinerie de réactions qui utilise la fonction reaction() pourrait certainement faire ces choses.

Je pense qu'il serait plus convaincant de voir un exemple qui distingue la machinerie de réaction de la logique du domaine.

Je suppose que vous voulez dire la manière dont la fonction doReactions() gère le démarrage/l'arrêt de XMLHttpRequest ? J'ai exploré différentes façons de le faire. Le problème est qu'il est difficile de trouver un moyen générique de détecter si deux réactions sont en fait la même réaction. L'isEqual de Lodash fonctionne presque, mais échoue pour les fermetures.

Je suppose que vous voulez dire la manière dont la fonction doReactions() gère le démarrage/l'arrêt de XMLHttpRequest ?

Non, je veux simplement dire que dans votre exemple, toute la configuration pour mettre en place le concept de réaction est mélangée à la logique de domaine des données extraites, ainsi qu'aux détails de la façon dont ces données sont extraites. Il me semble que les aspects génériques devraient être pris en compte dans quelque chose qui est moins couplé aux détails spécifiques à l'exemple.

Non, je veux simplement dire que dans votre exemple, toute la configuration pour mettre en place le concept de réaction est mélangée à la logique de domaine des données extraites, ainsi qu'aux détails de la façon dont ces données sont extraites. Il me semble que les aspects génériques devraient être pris en compte dans quelque chose qui est moins couplé aux détails spécifiques à l'exemple.

Hmm... Je pense que nous n'entendons peut-être pas la même chose par logique de domaine.

La façon dont je le vois, la fonction reaction() encapsule la logique du domaine et est distincte de la fonction doReactions() qui gère la logique de la façon dont les réactions sont appliquées. Mais vous semblez vouloir dire quelque chose de différent...

Dans l'état actuel des choses, vous ne pourriez pas vraiment faire cela dans la fonction de réactions. La logique là-bas aurait besoin de savoir quelles actions sont déjà commencées (nous ne pouvons rien y ajouter de plus), mais la fonction de réactions n'a pas les informations. La machinerie de réactions qui utilise la fonction reaction() pourrait certainement faire ces choses.

Je voulais principalement dire que si un seul événement déclenchait un changement d'état dans lequel plusieurs composants demandaient les mêmes informations, il pourrait alors être en mesure de les optimiser. Vous avez cependant raison de dire qu'il n'est pas suffisant en soi de déterminer si un effet secondaire d'un changement d'état précédent est toujours en attente et que la demande supplémentaire est donc inutile.

Au départ, je pensais que l'on pourrait peut-être conserver tous les états dans l'état de l'application, mais lorsque j'ai commencé à penser au récent problème du chronomètre, j'ai réalisé que même si le chronomètre isOn devait être stocké dans l'état de l'application, le interval réel isOn devrait être dans l'état de l'application, mais il n'est pas seul n'est pas un état suffisant dans ce cas.

Je voulais principalement dire que si un seul événement déclenchait un changement d'état dans lequel plusieurs composants demandaient les mêmes informations, il pourrait alors être en mesure de les optimiser. Vous avez cependant raison de dire qu'il n'est pas suffisant en soi de déterminer si un effet secondaire d'un changement d'état précédent est toujours en attente et que la demande supplémentaire est donc inutile.

Je pensais fusionner ou regrouper des demandes. L'élimination des doublons devrait fonctionner correctement. En fait, il devrait également gérer le cas des changements d'état en attente, car ils seront toujours renvoyés par la fonction de réactions (et donc dédupliqués) jusqu'à ce que la réponse du serveur revienne.

Au départ, je pensais que l'on pourrait peut-être conserver tous les états dans l'état de l'application, mais lorsque j'ai commencé à penser au récent problème du chronomètre, j'ai réalisé que même si le chronomètre est activé, il devrait être stocké dans l'état de l'application, l'objet d'intervalle réel associé à ce chronomètre doit être stocké ailleurs. isOn devrait être dans l'état de l'application mais n'est pas seul n'est pas un état suffisant dans ce cas.

La façon dont j'y pense, les réactions en attente actuelles sont comme vos composants de réaction. Techniquement, ils ont un état interne, mais nous les modélisons en fonction de l'état actuel.

Hmm... Je pense que nous n'entendons peut-être pas la même chose par logique de domaine.

La façon dont je le vois, la fonction reaction() encapsule la logique du domaine et est distincte de la fonction doReactions() qui gère la logique de la façon dont les réactions sont appliquées. Mais vous semblez vouloir dire quelque chose de différent...

J'ai en quelque sorte pris l'ensemble du module /reactions/index dans son ensemble, mais oui, je serais d'accord pour dire que la fonction reactions est purement logique de domaine. Mais plutôt que d'être dans un module spécifique à un domaine, il est enveloppé avec le passe-partout de doReactions . Cela ne va pas nuire à votre méthodologie, cela rend simplement plus difficile la compréhension d'un coup d'œil de la séparation entre le code de la bibliothèque et le code de l'application.

Ensuite, doReactions lui-même me semble être assez étroitement couplé à une méthode particulière de l'acte particulier récupérant des données à partir d'une API. Je suppose qu'une bibliothèque de réactions étoffée pourrait être un moyen d'enregistrer des gestionnaires pour différents types d'effets.

Ce n'est pas pour frapper votre méthode ; Je trouve cette approche vraiment intéressante.

Je ne suis pas sûr que l'état du composant réactif soit une bonne analogie car la plupart des états réactifs
devrait être dans l'état de l'application, mais il doit apparemment y avoir un moyen
pour maintenir l'état entre les événements de répartition qui ne peuvent pas être placés dans le
le magasin.

Je pense que ce genre d'état est ce que @yelouafi appelle l'état de contrôle et
peut-être que les sagas sont un bon moyen de modéliser l'état non sérialisable du
système en tant qu'observateur/acteur indépendant.

Je pense que je serais moins préoccupé par l'état caché de la saga si les sagas
répondu uniquement aux événements générés par l'application (réactions) au lieu de l'utilisateur
événements (actions) initiés car cela permettrait au réducteur d'application d'utiliser le
l'état actuel et toute logique métier conditionnelle pour déterminer si le
l'application doit permettre l'effet secondaire souhaité sans duplication
logique métier.
Le lundi 4 janvier 2016 à 17h56 Winston Ewert [email protected]
a écrit:

Je voulais surtout dire que si un seul événement déclenchait un changement d'état dans lequel
plusieurs composants ont demandé les mêmes informations, alors il pourrait être en mesure de
les optimiser. Vous avez cependant raison de dire qu'il ne suffit pas en soi de
déterminer si un effet secondaire d'un changement d'état précédent est toujours en attente
et donc la demande supplémentaire est inutile.

Je pensais fusionner ou regrouper des demandes. Éliminer les doublons
devrait fonctionner très bien. En fait, il devrait traiter le cas de l'état en attente
change aussi très bien, car ils seront toujours renvoyés du
les réactions fonctionnent (et donc dédupliquées) jusqu'à la réponse du serveur
revient.

Je pensais au départ que l'on pourrait peut-être conserver tous les états dans l'application
état, mais quand j'ai commencé à penser au récent problème de chronomètre, j'ai
s'est rendu compte que si le fait que le chronomètre est activé doit être stocké dans
l'application indique l'objet intervalle réel associé à cet
le chronomètre doit être stocké ailleurs. isOn devrait être dans l'application
état mais n'est pas seul n'est pas état suffisant dans ce cas.

La façon dont j'y pense, les réactions en attente actuelles sont comme votre
réagir les composants. Techniquement, ils ont un état interne, mais nous modélisons
eux en fonction de l'état actuel.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/rackt/redux/issues/1182#issuecomment-168858051 .

Cela ne va pas nuire à votre méthodologie, cela rend simplement plus difficile la compréhension d'un coup d'œil de la séparation entre le code de la bibliothèque et le code de l'application.

C'est tout à fait juste.

Ensuite, doReactions lui-même me semble être assez étroitement couplé à une méthode particulière de l'acte particulier récupérant des données à partir d'une API. Je suppose qu'une bibliothèque de réactions étoffée pourrait être un moyen d'enregistrer des gestionnaires pour différents types d'effets.

Oui. J'essaie toujours de trouver la meilleure façon de le diviser. C'est compliqué par la question de la vérification de l'égalité.

Je ne suis pas sûr que l'état du composant réactif soit une bonne analogie car la plupart des états réactifs
devrait être dans l'état de l'application, mais il doit apparemment y avoir un moyen
pour maintenir l'état entre les événements de répartition qui ne peuvent pas être placés dans le
le magasin.

Désolé, je pense que j'ai foiré l'analogie. Mon propos n'est pas tant de comparer l'état d'action externe à l'état du composant de réaction que l'état du DOM. L'intervalle ou XMLHttpRequest sont un peu comme les éléments DOM qui réagissent crée et détruit. Vous dites simplement à réagir ce que le DOM actuel devrait être et le faire se produire. De même, vous renvoyez simplement l'ensemble des réactions externes actuelles, et le cadre annule ou démarre l'action pour la rendre vraie.

Je trouve aussi cette approche très intéressante. Avez-vous envisagé d'utiliser plusieurs doReactions , qui prennent des mappages d'état différents ? Je pense que ce serait similaire à cyclejs, où vous pouvez créer des pilotes réutilisables :

function main(action$) {
  const state$ = action$.startWith(INITIAL_STATE).scan(reducer);

  return { 
    DOM: state$.map(describeDOM),
    HTTP: state$.map(describeRequests),
    ...
  };
}

Une différence est que vous n'interrogez pas les pilotes sur les événements pour obtenir le flux d'action ( const someEvent$ = sources.DOM.select('.class').events('click') ), mais spécifiez les actions dans le récepteur directement ( <button onClick={() => dispatch(action())} /> ) comme vous l'avez fait pour les requêtes HTTP également.

Je pense que l'analogie avec React fonctionne plutôt bien. Je ne considérerais pas le DOM comme l'état interne, mais plutôt l'API avec laquelle il fonctionne, tandis que l'état interne est composé des instances de composants et du dom virtuel.

Voici une idée pour l'API (en utilisant React ; HTTP pourrait également être construit comme ça):

// usage
const describe = (state, dispatch) => <MyComponent state={state} dispatch={dispatch} />;
const driver = createReactDOMDriver({ container } /* opts */);
store.subscribe(() => driver.update(describe(store.getState(), store.dispatch)); 
// (could be simplified further to eg. `store.use(driver, describe)` )

// implementation
const createReactDOMDriver = ({ container }) => {
  return {
    update: (element) => ReactDOM.render(element, container),
    destroy: () => ReactDOM.unmountComponentAtNode(container),
  };
};

Je voudrais que le describe prenne getState (plutôt qu'un instantané d'état) et dispatch . De cette façon, il pourrait être aussi asynchrone qu'il le souhaite.

Avez-vous envisagé d'utiliser plusieurs doReactions, qui prennent différents mappages d'état ?

J'y avais brièvement pensé, et j'y vais un peu en ce moment. Il est donc naturel d'avoir différentes bibliothèques de réactions qui font des choses différentes, une pour le DOM, une pour http, une pour les temporisateurs, une pour l'audio Web, etc. Chacun peut faire les optimisations/comportements appropriés à son propre cas. Mais cela semble moins utile si vous avez une application qui effectue un tas d'actions externes ponctuelles.

Je voudrais que le descriptif prenne getState (plutôt qu'un instantané d'état) et envoie. De cette façon, il pourrait être aussi asynchrone qu'il le souhaite.

Je ne le ferais pas. À mon avis, nous voulons restreindre l'async dans la mesure du possible, et non fournir des moyens supplémentaires de l'utiliser. Tout ce que vous voudrez peut-être appeler getState() doit être fait dans la fonction de réduction ou de réactions. (Mais c'est mon état d'esprit puriste, et il y a peut-être un argument pragmatique pour ne pas le suivre.)

Point juste. Je n'ai pas bien réfléchi au mappage entre votre idée et l'exemple de @taurose . J'ai supposé à la hâte que describe était la fonction reactions , mais ce n'est peut-être pas vrai.

Mais oui, je suis d'accord pour dire que limiter l'async est idéal, car si je comprends l'idée maîtresse de votre idée, nous voulons que les continuations soient pures et mappent 1:1 avec des aspects spécifiques de l'état, comme la présence d'un membre du tableau décrivant l'intention que un effet donné est en cours. De cette façon, peu importe s'ils sont exécutés plusieurs fois, et il n'y a aucun aspect caché d'un processus bloqué quelque part au milieu du flux dont d'autres processus pourraient implicitement dépendre.

Je voudrais que le descriptif prenne getState (plutôt qu'un instantané d'état) et envoie. De cette façon, il pourrait être aussi asynchrone qu'il le souhaite.

describe est appelé à chaque changement d'état, donc je n'en vois pas la nécessité. Cela ne veut pas dire qu'il ne peut pas faire async. Considérez les composants de réaction : vous n'appelleriez pas getState intérieur de vos méthodes de rendu ou de vos gestionnaires d'événements pour obtenir l'état actuel, mais plutôt le lisez à partir des accessoires.

Mais vous avez raison, il ne peut (ne devrait pas) faire quoi que ce soit d'asynchrone par lui-même ; il devrait laisser cela au pilote et lui transmettre simplement un état mappé et/ou des rappels.

supposé décrire était la fonction des réactions, mais cela peut ne pas être vrai.

Autant que je sache, c'est à peu près la même chose. Une différence serait que reactions n'obtient pas dispatch . Ainsi, alors que describe renvoie des rappels qui créent et distribuent des actions, reactions renvoie les créateurs d'actions.

@winstonewert c'est un long fil et je n'ai pas le temps de lire pour le moment ou de vérifier votre code mais peut-être que @yelouafi pourra vous répondre.

Le projet redux-saga est né de longues discussions ici

J'utilise également le concept de saga depuis plus d'un an sur une application de production, et l'implémentation est moins expressive mais pas basée sur des générateurs. Voici quelques pseudo exemples que j'ai donnés du concept de redux :

La mise en œuvre ici est loin d'être parfaite mais cela donne juste une idée.

@yelouafi est conscient des problèmes inhérents à l'utilisation de générateurs qui masquent l'état en dehors du redux, et qu'il est compliqué de démarrer une saga sur un backend, et de transmettre cet état caché au frontend pour les applications universelles (si vraiment nécessaire ?)

La saga redux est à redux-thunk comme Free est à IO monad. Les effets sont déclaratifs et non exécutés pour le moment, peuvent être introspectés et exécutés dans un interpréteur (que vous pourrez personnaliser à l'avenir)

Je comprends votre point sur l'état caché à l'intérieur des générateurs. Mais en réalité, le magasin Redux est-il la véritable source de vérité d'une application Redux ? Je ne pense pas. Redux enregistre les actions et les rejoue. Vous pouvez toujours rejouer ces actions pour recréer le magasin. Le magasin redux est comme une vue de requête CQRS du journal des événements. Cela ne signifie pas qu'il doit s'agir de la seule projection de ce journal des événements. Vous pouvez projeter le même journal d'événements dans différentes vues de requêtes, et les écouter dans des sagas qui peuvent gérer leur état avec des générateurs, des objets mutables globaux ou des réducteurs, quelle que soit la technologie.

À mon humble avis, créer le concept de saga avec un réducteur n'est pas une mauvaise idée sur le plan conceptuel, et je suis d'accord avec vous, c'est une décision d'échange.
Personnellement après plus d'1 an d'utilisation des sagas en production je ne me souviens d'aucun cas d'utilisation où il aurait été utile de pouvoir prendre un instantané de l'état d'une saga et de la restaurer plus tard, donc je préfère l'expressivité des générateurs même si je perds ça caractéristique.

J'espère que rien de ce que je dis n'est apparu comme une attaque contre redux-saga. Je parlais juste de la différence avec l'approche que j'avais imaginée.

Je comprends votre point sur l'état caché à l'intérieur des générateurs. Mais en réalité, le magasin Redux est-il la véritable source de vérité d'une application Redux ? Je ne pense pas. Redux enregistre les actions et les rejoue. Vous pouvez toujours rejouer ces actions pour recréer le magasin. Le magasin redux est comme une vue de requête CQRS du journal des événements. Cela ne signifie pas qu'il doit s'agir de la seule projection de ce journal des événements. Vous pouvez projeter le même journal d'événements dans différentes vues de requêtes, et les écouter dans des sagas qui peuvent gérer leur état avec des générateurs, des objets mutables globaux ou des réducteurs, quelle que soit la technologie.

Je ne comprends pas vraiment votre point ici. Vous semblez soutenir qu'une saga est une projection du journal des événements ? Mais ce n'est pas. Si je rejoue les actions, je n'arriverai pas à la même place dans les sagas si la saga dépend d'événements asynchrones. Il me semble incontournable que les sagas produisent un état qui n'est ni dans le magasin d'état de redux ni une projection du journal des événements.

Autant que je sache, c'est à peu près la même chose. Une différence serait que les réactions ne sont pas expédiées. Ainsi, alors que describe renvoie des rappels qui créent et distribuent des actions, les réactions renvoient des créateurs d'actions.

D'accord. En principe, react pourrait utiliser la même interface, tous les gestionnaires d'événements prendraient un créateur d'action qui serait envoyé lorsque l'événement se déclencherait.

Plus j'y pense, plus je pense qu'il pourrait y avoir beaucoup de synergie entre cette approche et les sagas. Je suis tout à fait d'accord avec les quatre points soulignés par @winstonewert. Je pense que c'est une bonne chose que les réactions ne puissent pas voir les actions initiées par l'utilisateur car cela empêche l'état caché et garantit que la logique métier dans les réducteurs n'a pas besoin d'être dupliquée dans les créateurs d'actions ou les sagas. Cependant, j'ai réalisé que les effets secondaires créent souvent un état non sérialisable qui ne peut pas être stocké dans le magasin de réaction, des intervalles, des objets dom, des requêtes http, etc. les sagas, rxjs, baconjs, etc. sont parfaits pour cet état de contrôle externe non sérialisable.

doReactions pourrait être remplacé par une saga et la source d'événement pour les sagas devrait être des réactions et non des actions.

J'espère que rien de ce que je dis n'est apparu comme une attaque contre redux-saga

Pas du tout. J'ai suivi la discussion mais je n'ai pas voulu commenter sans regarder de plus près votre code.

À première vue. Il semble que vous ne réagissiez qu'aux changements d'état. Comme je l'ai dit, c'était un coup d'œil rapide. Mais il semble que cela rendra la mise en œuvre de flux complexes encore plus difficile que l'approche elm (où vous prenez à la fois l'état et l'action). cela signifie que vous devrez stocker encore plus d'états de contrôle dans le magasin (où les changements d'état de l'application sont à eux seuls insuffisants pour déduire les réactions pertinentes)

Bien sûr, rien ne peut battre des fonctions pures. Je pense que les réducteurs sont parfaits pour exprimer les transitions d'état, mais deviennent vraiment étranges lorsque vous les transformez en machines à états.

cela signifie que vous devrez stocker encore plus d'états de contrôle dans le magasin (où les changements d'état de l'application sont à eux seuls insuffisants pour déduire les réactions pertinentes)

Ouais. Cela me semble être l'aspect différenciant clé de cette approche. Mais je me demande si ce problème pourrait être rendu transparent, en pratique, si différents types d'effets peuvent être enveloppés dans différents "pilotes" ? J'imagine qu'il est assez facile pour les gens de simplement choisir les pilotes qu'ils veulent ou d'écrire les leurs pour de nouveaux effets.

Cependant, j'ai réalisé que les effets secondaires créent souvent un état non sérialisable qui ne peut pas être stocké dans le magasin de réaction, des intervalles, des objets dom, des requêtes http, etc. les sagas, rxjs, baconjs, etc. sont parfaits pour cet état de contrôle externe non sérialisable.

Je ne vois pas encore ce que tu es.

Je pense que les réducteurs sont parfaits pour exprimer les transitions d'état, mais deviennent vraiment étranges lorsque vous les transformez en machines à états.

Je suis d'accord. Si vous écrivez à la main une machine à états complexe, nous avons un problème. (En fait, ce serait bien si nous pouvions convertir un générateur en un réducteur).

Mais je me demande si ce problème pourrait être rendu transparent, en pratique, si différents types d'effets peuvent être enveloppés dans différents "pilotes" ? J'imagine qu'il est assez facile pour les gens de simplement choisir les pilotes qu'ils veulent ou d'écrire les leurs pour de nouveaux effets.

Je ne suis pas sûr de ce que vous pensez ici. Je peux voir différents pilotes faire différentes choses utiles, mais éliminer l'état de contrôle ?

@winstonewert non, je ne prends rien comme une attaque. Je n'ai même pas eu le temps de vraiment regarder ton code :)

Je ne comprends pas vraiment votre point ici. Vous semblez soutenir qu'une saga est une projection du journal des événements ? Mais ce n'est pas. Si je rejoue les actions, je n'arriverai pas à la même place dans les sagas si la saga dépend d'événements asynchrones. Il me semble incontournable que les sagas produisent un état qui n'est ni dans le magasin d'état de redux ni une projection du journal des événements.

Non, je ne le suis pas, le redux store est une projection, mais la saga est un vieux simple auditeur.

La saga (également appelée gestionnaire de processus) n'est pas un nouveau concept, elle provient du monde CQRS et a été largement utilisée sur les systèmes backend dans le passé.

La saga n'est pas la projection d'un journal d'événements sur une structure de données, c'est un morceau d'orchestration qui peut écouter ce qui se passe dans votre système et émettre des réactions, le reste est des détails de mise en œuvre. Généralement, les sagas écoutent un journal d'événements (et peut-être d'autres choses externes, comme l'heure...) et peuvent produire de nouvelles commandes/événements. De plus, lorsque vous rejouez des événements dans des systèmes backend, vous désactivez généralement les effets secondaires déclenchés par les sagas.

Une différence est que dans les systèmes backend, la saga est souvent en réalité une projection du journal des événements : pour changer son état, il doit émettre des événements et les écouter lui-même. Dans redux-saga tel qu'il est actuellement implémenté, il serait plus difficile de rejouer le journal des événements pour restaurer l'état de la saga.

Je ne suis pas sûr de ce que vous pensez ici. Je peux voir différents pilotes faire différentes choses utiles, mais éliminer l'état de contrôle ?

Non, je ne l'élimine pas, mais en fait simplement une préoccupation de mise en œuvre sous le capot, dans la plupart des cas.

Il me semble qu'il y a un très fort consensus au sein de la communauté Redux sur le fait que le stockage de l'état du domaine dans le magasin est une énorme victoire (sinon, pourquoi utiliseriez-vous Redux ?). Un peu moins est le consensus selon lequel le stockage de l'état de l'interface utilisateur est une victoire, par opposition à son encapsulation dans des composants. Ensuite, il y a l'idée de synchroniser l'état du navigateur dans le magasin, comme l'URL (redux-simple-router) ou les données de formulaire. Mais cela semble être la dernière frontière, celle de stocker le statut/l'étape d'un processus de longue haleine dans le magasin.

Désolé s'il s'agit d'une tangente, mais je pense qu'une approche très générale avec une bonne convivialité pour les développeurs devrait avoir les caractéristiques suivantes :

  • Faites en sorte que l'utilisateur type n'ait pas vraiment à se soucier de la façon dont les effets sont représentés dans le magasin. Ils doivent interagir avec des API simples, qui résument ces détails.
  • Faites en sorte que les effets soient facilement composables. Il devrait sembler naturel de faire des choses comme contrôler le flux et les effets qui dépendent d'autres effets. C'est, bien sûr, là où une abstraction de générateur brille vraiment. Il fonctionne bien avec la plupart des flux de contrôle, les fermetures étant une exception notable. Mais il est facile de voir à quel point les flux asynchrones complexes sont exprimables dans redux-saga ou react-redux-controller.
  • Faites en sorte que le statut de l'effet puisse être facilement présenté aux autres consommateurs du magasin, si vous le souhaitez. Cela vous permettrait de faire des choses comme présenter l'état d'un processus multi-effets à l'utilisateur.
  • C'est peut-être évident, mais tout sous-système qui encapsule l'état synchronise cet état avec Redux, en envoyant des actions.

Pour ce deuxième point, je pense qu'il devrait y avoir quelque chose d'assez similaire à redux-saga. Il peut être assez proche de ce que j'ai en tête avec ses call wrappers. Mais une saga devrait être "à avance rapide", dans un sens, pour vous permettre de la désérialiser dans un état intermédiaire.

C'est une sorte de défi de taille, mais en pratique, je pense que s'il y a de gros gains à obtenir en ayant un enregistrement d'action central et sérialisable, suivant l'état d'une application entière à un niveau très granulaire, ce serait le moyen de l'exploiter. Et je pense qu'il peut en effet y avoir de grandes victoires là-bas. J'imagine un moyen beaucoup plus simple d'instrumenter des applications avec des analyses d'utilisateurs et de performances. J'imagine une testabilité vraiment incroyable, où différents sous-systèmes ne sont couplés qu'à travers l'état.

J'ai peut-être dévié du cours maintenant, alors je vais en rester là :)

@acjay je pense qu'on est d'accord avec toi sur ces points, le problème c'est de trouver cette implémentation qui résout tout ça correctement :)

Mais il semble difficile d'avoir à la fois une API expressive avec des générateurs, et la possibilité de voyager dans le temps et d'état instantané/restauration... Peut-être serait-il possible de mémoriser l'exécution de l'effet afin que nous puissions facilement restaurer l'état des générateurs...

Pas sûr, mais cela pourrait empêcher les sagas de style while(true) { ... } . Le bouclage ne serait-il qu'une conséquence de la progression de l'état ?

@acjay @slorber

Comme je l'ai expliqué dans (https://github.com/yelouafi/redux-saga/issues/22#issuecomment-168872101) Voyager dans le temps seul (c'est-à-dire sans rechargement à chaud) est possible pour les sagas. Tout ce dont vous avez besoin pour amener une saga à un point spécifique est la séquence d'effets produite depuis le début jusqu'à ce point, ainsi que leur résultat (résolution ou rejet). Ensuite, vous conduirez simplement le générateur avec cette séquence

Dans la branche master actuelle (pas encore publiée sur npm). Les sagas supportent le suivi, elles expédient tous les effets produits, ainsi que leurs dénouements sous forme d'actions au magasin ; ils fournissent également des informations de hiérarchie pour tracer le graphe de flux de contrôle.

Cet effet log peut être exploité pour rejouer une Saga jusqu'à un moment donné : pas besoin de faire les vrais appels api puisque le log contient déjà les réponses passées.

Dans les exemples de dépôt, il y a un exemple de moniteur de saga (implémenté en tant que middleware Redux). Il écoute le journal des effets et maintient une arborescence interne (bien construite paresseusement). Vous pouvez imprimer une trace du flux en envoyant une action {type: 'LOG_EFFECT'} au magasin

Voici une capture d'un journal des effets de l'exemple asynchrone

saga-log-async

Edit: désolé lien d'image fixe

Intrigant! Et cette image des outils de développement est _awesome_.

C'est super :)

En effet, ce moniteur de saga est plutôt cool.

En y réfléchissant, il me semble que la saga résout deux problèmes. Premièrement, il gère les effets asynchrones. Deuxièmement, il gère des interactions d'état complexes qui, autrement, auraient nécessité une machine d'état écrite à la main odieuse dans un réducteur.

Mon approche n'aborde que le premier problème. Je n'ai pas trouvé de besoin pour le deuxième numéro. Je n'ai probablement pas encore écrit assez de code redux pour l'exécuter.

Oui, mais je me demande s'il y a un moyen de fusionner les deux idées. Le wrapper call redux-saga est un niveau assez simple d'indirection sur un effet, mais en supposant que vous puissiez initialiser le middleware avec des pilotes pour différents types d'effets, vous pouvez les représenter ces effets sous forme de données JSONable, découplées de la fonction c'est en fait ce qu'on appelle. Le conducteur s'occuperait des détails de l'envoi des changements d'état sous-jacents au magasin.

Cela pourrait représenter beaucoup de complexité supplémentaire pour peu d'avantages pratiques. Mais j'essaie juste de suivre cette ligne de pensée jusqu'au bout.

Ok, j'ai mis en place plus d'une bibliothèque et porté l'exemple du monde réel pour l'utiliser :

Premièrement, nous avons la mise en œuvre de réactions :
https://github.com/winstonewert/redux-reactions/blob/master/src/index.js
L'interface est constituée de trois fonctions : startReactions prend le magasin, une fonction de réactions et un mappage des noms vers les pilotes. fromEmitter et fromPromiseFactory créent tous deux des pilotes.

Ici, l'exemple appelle startReactions pour activer le système :
https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/store/configureStore.dev.js#L28

La configuration de base des réactions est ici :
https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/index.js.
La fonction de réactions parcourt en fait les composants qui réagissent instancie le routeur à la recherche de ceux avec une fonction réactions () pour déterminer les réactions réellement nécessaires pour cette page.

L'implémentation du type de réaction github api est ici : https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/api.js. Il s'agit principalement de copier/coller à partir du middleware utilisé dans l'exemple d'origine. Le point critique est ici : https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/api.js#L79 , où il utilise fromPromiseFactory pour créer le pilote à partir d'une fonction qui renvoie des promesses.

Voir une fonction de réaction spécifique à un composant ici : https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/containers/RepoPage.js#L80.

Les créateurs de réaction et la logique commune se trouvent dans

Salut les gens! Raise vient de publier un amplificateur de magasin qui vous permet également d'utiliser un système d'effets semblable à l'architecture Elm ! J'espère que nous pourrons apprendre et améliorer toutes ces approches à l'avenir pour répondre à tous les besoins de la communauté :smile:

https://github.com/raisemarketplace/redux-loop

Toute personne intéressée par la discussion voudra peut-être en savoir plus sur mon idée ici : https://github.com/winstonewert/redux-reactions/issues/7

Vous pouvez également consulter une branche ici, où j'ai retravaillé l'application de compteur pour qu'elle soit plus elmish en utilisant mon modèle :
https://github.com/winstonewert/redux-reactions/tree/elmish/examples/counter

J'ai aussi découvert que je réinvente l'approche utilisée ici : https://github.com/ccorcos/elmish

Salut @yelouafi , pourrais-tu republier le lien vers l'idée du moniteur de la saga ? C'est vraiment des trucs super ! Le lien semble mort(404). J'aimerais en voir plus !

Nouvelle discussion pertinente : https://github.com/reactjs/redux/issues/1528

(Je crois que c'est lié. Désolé si ce n'est pas le bon endroit)

Pourrions-nous éventuellement traiter tous les effets de la même manière que le rendu DOM ?

  1. jQuery est un pilote DOM avec une interface impérative. React est un pilote DOM avec une interface déclarative. Ainsi, au lieu d'ordonner : « ne désactiver ce bouton », nous déclarons : « nous avons besoin de ce bouton désactivé » et le pilote décide des manipulations DOM à faire. Au lieu de commander : " GET \product\123 ", nous déclarons : " nous avons besoin de ces données " et le pilote décide quelles demandes envoyer/annuler.
  2. Nous utilisons des composants React comme pilote API vers DOM. Utilisons-les également pour nous connecter à d'autres pilotes.

    • <button ...> - nous construisons notre couche View à partir de composants React "normaux"

    • <Map ...> - nous utilisons des composants "wrapper" pour transformer l'interface impérative d'une bibliothèque en une interface déclarative. Nous les utilisons de la même manière que les composants "normaux", mais en interne ce sont en fait des pilotes.

    • <Chart ...> - il peut s'agir de l'un des éléments ci-dessus en fonction de l'implémentation. Ainsi, la frontière entre les composants "normaux" et les pilotes est déjà floue.

    • <Http url={'/product/'+props.selectedProductId} onSuccess={props.PRODUCT_LOADED} /> (ou "intelligent" <Service...> ) - nous construisons notre couche de service à partir de composants de pilote (sans interface utilisateur)

Les couches View et Service sont décrites via les composants React. Et nos composants de haut niveau (connectés) les collent ensemble.
De cette façon, nos réducteurs restent purs et nous n'introduisons aucun nouveau moyen de gérer les effets.

Je ne sais pas comment new Date ou Math.random s'intègrent ici.

Est-il toujours possible de convertir une API impérative en une API déclarative ?
Pensez-vous que ce point de vue soit viable ?

Merci

Étant donné que nous avons des sagas et d'autres outils impressionnants pour les actions asynchrones, je pense que nous pouvons clore cela en toute sécurité maintenant. Consultez le n° 1528 pour de nouvelles directions intéressantes (au-delà de l'async aussi).

Cette page vous a été utile?
0 / 5 - 0 notes