Redux: Quels sont les inconvénients de stocker tout votre état dans un seul atome immuable?

Créé le 10 févr. 2016  ·  91Commentaires  ·  Source: reduxjs/redux

Je comprends que c'est le principe qui sous-tend tout redux, et qu'il vient avec tous ces avantages impressionnants qui sont assez bien connus maintenant.

Cependant, je pense qu'un endroit qui manque au redux, c'est qu'il ne décrit pas ouvertement les inconvénients conceptuels de l'utilisation de cette architecture. C'est peut-être un bon choix, car vous voulez que les gens ne fuient pas en raison des inconvénients.

Je suis juste curieux parce que cela s'applique non seulement au redux, mais dans une certaine mesure à d'autres choses comme Om , datomic et qui parlent de transformer la base de données à l'envers . J'aime redux, j'aime Om, je m'intéresse à la datomic, et j'ai vraiment aimé ce discours.

Mais malgré toutes les recherches que j'ai effectuées, il est difficile de trouver des personnes critiques à l'égard du seul magasin immuable. Ceci est un exemple, mais il semble juste avoir un problème plus avec la verbosité du redux qu'avec l'architecture réelle.

La seule chose à laquelle je peux penser est qu'il faut peut-être plus de mémoire pour continuer à stocker des copies de votre état, ou qu'il est un peu plus difficile de protéger rapidement avec redux.

Parce que vous avez probablement réfléchi beaucoup plus à cela que moi, pourriez-vous m'aider à élucider cela?

discussion docs question

Commentaire le plus utile

Redux est essentiellement une source d'événements où il n'y a qu'une seule projection à consommer.

Dans les systèmes distribués, il existe généralement un journal des événements (comme Kafka) et plusieurs consommateurs qui peuvent projeter / réduire ce journal dans plusieurs bases de données / magasins hébergés sur différents serveurs (généralement, un réplica de base de données est en fait un réducteur). Ainsi, dans un monde distribué, la charge et l'utilisation de la mémoire sont ... distribuées, tandis que dans Redux, si vous avez des centaines de widgets qui ont tous leur état local, tout cela s'exécute dans un seul navigateur, et chaque changement d'état a une surcharge en raison de l'immuable mises à jour des données.

Dans la plupart des cas, cette surcharge n'est pas un gros problème, mais lors du montage des entrées de texte dans un état pas si bon et de la saisie rapide sur un appareil mobile lent, ce n'est pas toujours assez performant.

Rendre cet état depuis le sommet n'est, d'après mon expérience, pas pratique et pas toujours assez performant (du moins avec React, qui n'est probablement pas l'implémentation VDom la plus rapide). Redux résout cela avec connect mais quand même, à mesure que le nombre de connexions augmente, cela peut devenir un goulot d'étranglement. Il existe des solutions

De plus, les structures de données persistantes comme ImmutableJS n'offrent pas toujours les meilleures performances sur certaines opérations, comme l'ajout d'un élément à un index aléatoire dans une grande liste (voir mon expérience de rendu de grandes listes ici )

Redux inclut à la fois le journal des événements et la projection car c'est pratique et convient à la plupart des cas d'utilisation, mais il pourrait être possible de maintenir un journal des événements en dehors de redux et de le projeter dans 2 magasins redux ou plus, ajoutez tous ces magasins redux à réagit au contexte sous différentes clés, et a moins de frais généraux en spécifiant à quel magasin nous voulons nous connecter. Ceci est absolument possible, mais cela rendrait l'api et les outils de développement plus difficiles à implémenter car vous avez maintenant besoin d'une distinction claire entre le magasin et le journal des événements.


Je suis également d'accord avec @jquense

Les avantages d'une hydratation facile, des instantanés, des voyages dans le temps, etc. ne fonctionnent que s'il n'y a pas d'autre endroit important où vit un état. Dans le contexte de React, cela signifie que vous devez stocker l'état, qui appartient correctement aux composants du magasin, ou perdre de nombreux avantages. Si vous voulez tout mettre dans Redux, cela finit souvent par être fastidieux et verbeux, et ajoute un niveau d'indirection ennuyeux.

Le montage de n'importe quel état sur le magasin Redux nécessite plus de passe-partout. L'architecture Elm résout probablement cela d'une manière plus élégante, mais nécessite également beaucoup de passe-partout.

Mais il n'est pas non plus possible ou performant de rendre tous les états contrôlés. Parfois, nous utilisons des bibliothèques existantes pour lesquelles il est difficile de construire une interface déclarative. Certains états sont également difficiles à monter sur le magasin redux de manière efficace, notamment:

  • Entrées de texte
  • Position de défilement
  • Taille de la fenêtre
  • Position de la souris
  • Position / sélection du curseur
  • Données du canevas
  • État non sérialisable
  • ...

Tous les 91 commentaires

C'est une excellente question. J'essaierai d'écrire une réponse complète demain, mais j'aimerais d'abord entendre certains de nos utilisateurs. Ce serait bien de résumer cette discussion plus tard dans une page de documentation officielle. Nous ne l'avons pas fait au début car nous ne connaissions pas les inconvénients.

Merci beaucoup! J'avais vraiment envie d'en entendre parler.

En passant, j'encourage tous ceux qui souhaitent contribuer à cette discussion à donner également une idée de ce qu'ils construisaient avec Redux afin qu'il soit plus facile de comprendre la portée des projets.

Son genre de question difficile à cause de projets comme OM et redux et d'autres bibliothèques atomiques à un seul état, atténue activement les inconvénients. Sans la contrainte d'immuabilité et l'accès contrôlé et contrôlé, un seul atome d'état n'est pas du tout différent de l'attachement de toutes vos données au window (dont les inconvénients sont bien connus)

Spécifique à l'approche Redux cependant, le plus gros inconvénient pour nous est que les atomes à un seul état sont une sorte de tout ou rien. Les avantages d'une hydratation facile, des instantanés, des voyages dans le temps, etc. ne fonctionnent que s'il n'y a _aucun autre endroit important. Dans le contexte de React, cela signifie que vous devez stocker l'état, qui appartient correctement aux composants du magasin, ou perdre de nombreux avantages. Si vous voulez tout mettre dans Redux, cela finit souvent par être fastidieux et verbeux, et ajoute un niveau d'indirection ennuyeux.

Aucun de ces inconvénients n'a été particulièrement prohibitif pour nous :)

Donc, j'ai regardé Redux, et ce style d'architecture, pour modéliser les états du jeu; J'ai un intérêt à créer des mondes simulés de style Civ, et des histoires de jeux de société entièrement enregistrées plus simplement, dans un contexte de navigateur, ce qui est peut-être un cas d'utilisation inhabituel pour certains, mais je doute que quiconque veuille que je m'arrête non plus.

Dans ce contexte, je m'intéresse aux efforts visant à gérer la taille de la structure des données d'historique, à en déplacer des parties vers le serveur ou le disque pour la sauvegarder, à restaurer des segments spécifiques, etc.

La chose avec laquelle j'ai eu du mal, en tant que nouvel utilisateur, est d'essayer de surmonter à la fois le changement extrême de style de codage (imho) dans Redux et les changements conceptuels en même temps; cela reste un peu intimidant même maintenant, un mois et changer après avoir essayé de s'y plonger profondément; Je regarde ensuite https://github.com/ngrx/store pour obtenir les concepts, mais avec un style / syntaxe plus familier et plus du reste du cadre implicite / fourni.

Je comprends que pour certains, un cadre est une béquille; mais certains d'entre nous en ont besoin; peu de gens seront suffisamment au sommet de leur forme pour avoir des opinions fortes utiles, donc un framework est préférable à tâtonner dans le noir, vous savez? C'est donc un point de ma part, un programmeur d'âge moyen de niveau intermédiaire, nouveau dans les concepts :)

En gros, Redux et vos vidéos m'ont appris à le faire en javascript brut, mais étant un programmeur moins expérimenté, je n'ai toujours aucune idée de comment livrer un produit sans les conseils supplémentaires, donc jusqu'à ce que je voie cet exemple de chose finie, je juste un peu se tenir comme:

Ce n'est pas de votre faute, mais c'est toujours un problème que j'aimerais aider à résoudre :)

Je pense que la plus grande question que je me pose encore à ce stade est la suivante: où devraient aller les éléments non sérialisables tels que les fonctions, les instances ou les promesses? Je me posais des questions à ce sujet dans Reactiflux l'autre soir et je n'ai pas obtenu de bonnes réponses. Je viens également de voir quelqu'un publier http://stackoverflow.com/questions/35325195/should-i-store-function-references-in-redux-store .

Cas d'utilisation pour plusieurs magasins (gardez à l'esprit que c'est le meilleur auquel je puisse penser):

Une application qui combine plusieurs sous-applications ensemble. Pensez à quelque chose comme My Yahoo ou un autre produit de page d'accueil personnalisable. Chaque widget devra conserver son propre état sans chevauchement entre les deux. Une équipe produit est probablement affectée à chaque widget, elle peut donc ne pas connaître les effets du code des autres sur l'environnement.

Réunis, vous pouvez probablement y parvenir dans Redux en violant quelques règles et en faisant attention à propager ces magasins sur quelque chose comme le contexte de React. Mais cela pourrait être plus facile avec un framework qui gère plusieurs atomes d'état par défaut.

@gaearon nous construisons une plateforme de trading. Je vous ai déjà expliqué une fois pourquoi un magasin ne nous convenait pas (après la rencontre de React.js à Saint-Pétersbourg). Je vais essayer de l'expliquer à nouveau en détails (dans un article de blog sur support?) Mais j'ai probablement besoin d'aide en raison de mes compétences en anglais :) Est-ce que je peux vous l'envoyer ou à quelqu'un d'autre ici pour examen direct sur Twitter messages quand ce sera fait? Je ne peux pas nommer la date exacte mais j'essaierai d'écrire ce post bientôt (probablement à la fin de ce mois).

Et oui, nous utilisons toujours Redux avec plusieurs magasins en violant certaines règles de la documentation, comme l' a dit react-redux aussi confortablement dans les cas où nous aurions besoin de données provenant de divers magasins. en cas de magasin unique)

Redux est essentiellement une source d'événements où il n'y a qu'une seule projection à consommer.

Dans les systèmes distribués, il existe généralement un journal des événements (comme Kafka) et plusieurs consommateurs qui peuvent projeter / réduire ce journal dans plusieurs bases de données / magasins hébergés sur différents serveurs (généralement, un réplica de base de données est en fait un réducteur). Ainsi, dans un monde distribué, la charge et l'utilisation de la mémoire sont ... distribuées, tandis que dans Redux, si vous avez des centaines de widgets qui ont tous leur état local, tout cela s'exécute dans un seul navigateur, et chaque changement d'état a une surcharge en raison de l'immuable mises à jour des données.

Dans la plupart des cas, cette surcharge n'est pas un gros problème, mais lors du montage des entrées de texte dans un état pas si bon et de la saisie rapide sur un appareil mobile lent, ce n'est pas toujours assez performant.

Rendre cet état depuis le sommet n'est, d'après mon expérience, pas pratique et pas toujours assez performant (du moins avec React, qui n'est probablement pas l'implémentation VDom la plus rapide). Redux résout cela avec connect mais quand même, à mesure que le nombre de connexions augmente, cela peut devenir un goulot d'étranglement. Il existe des solutions

De plus, les structures de données persistantes comme ImmutableJS n'offrent pas toujours les meilleures performances sur certaines opérations, comme l'ajout d'un élément à un index aléatoire dans une grande liste (voir mon expérience de rendu de grandes listes ici )

Redux inclut à la fois le journal des événements et la projection car c'est pratique et convient à la plupart des cas d'utilisation, mais il pourrait être possible de maintenir un journal des événements en dehors de redux et de le projeter dans 2 magasins redux ou plus, ajoutez tous ces magasins redux à réagit au contexte sous différentes clés, et a moins de frais généraux en spécifiant à quel magasin nous voulons nous connecter. Ceci est absolument possible, mais cela rendrait l'api et les outils de développement plus difficiles à implémenter car vous avez maintenant besoin d'une distinction claire entre le magasin et le journal des événements.


Je suis également d'accord avec @jquense

Les avantages d'une hydratation facile, des instantanés, des voyages dans le temps, etc. ne fonctionnent que s'il n'y a pas d'autre endroit important où vit un état. Dans le contexte de React, cela signifie que vous devez stocker l'état, qui appartient correctement aux composants du magasin, ou perdre de nombreux avantages. Si vous voulez tout mettre dans Redux, cela finit souvent par être fastidieux et verbeux, et ajoute un niveau d'indirection ennuyeux.

Le montage de n'importe quel état sur le magasin Redux nécessite plus de passe-partout. L'architecture Elm résout probablement cela d'une manière plus élégante, mais nécessite également beaucoup de passe-partout.

Mais il n'est pas non plus possible ou performant de rendre tous les états contrôlés. Parfois, nous utilisons des bibliothèques existantes pour lesquelles il est difficile de construire une interface déclarative. Certains états sont également difficiles à monter sur le magasin redux de manière efficace, notamment:

  • Entrées de texte
  • Position de défilement
  • Taille de la fenêtre
  • Position de la souris
  • Position / sélection du curseur
  • Données du canevas
  • État non sérialisable
  • ...

Cette question que je viens de voir sur SO:

http://stackoverflow.com/questions/35328056/react-redux-should-all-component-states-be-kept-in-redux-store

Échos une certaine confusion que j'ai vue sur la gestion de l'état de l'interface utilisateur et si l'état de l'interface utilisateur doit appartenir au composant ou entrer dans le magasin. Le # 1287 offre une bonne réponse à cette question, mais ce n'est pas si évident au départ et peut faire l'objet d'un débat.

De plus, cela pourrait être un obstacle si vous essayez d'implémenter quelque chose comme ceci https://github.com/ericelliott/react-pure-component-starter dans lequel chaque composant est pur et n'a pas son mot à dire dans son propre état .

J'ai trouvé difficile d'utiliser l'arborescence d'état unique de redux lorsque je devais gérer les données de plusieurs sessions. J'avais un nombre arbitraire de branches avec une forme identique et chacune avait plusieurs identifiants uniques (en fonction de l'origine des données, vous utiliseriez une clé différente). La simplicité des fonctions de réduction et de resélection a rapidement disparu face à ces exigences - avoir à écrire des getters / setters personnalisés pour cibler une branche spécifique semblait trop complexe et plat dans un environnement par ailleurs simple et compossible. Je ne sais pas si plusieurs magasins sont la meilleure option pour répondre à cette exigence, mais certains outils autour de la gestion des sessions (ou toute autre donnée de forme identique) dans un seul arbre d'état serait bien.

Augmentation de la probabilité de collisions de clés d'état entre les réducteurs.
Les mutations et déclarations de données sont loin du code où les données sont utilisées (lorsque nous écrivons un code, nous essayons de placer des déclarations de variables près de l'endroit où nous les utilisons)

Permettez-moi de commencer avec mon cas d'utilisation particulier: j'utilise Redux avec virtual-dom, où tous mes composants d'interface utilisateur sont purement fonctionnels et il n'y a aucun moyen d'avoir un état local pour divers composants.

Cela dit, la partie la plus difficile est certainement de lier l' état d'animation . Les exemples suivants ont à l'esprit l'état d'animation, mais une grande partie de cela peut être généralisée à l' état de l'interface utilisateur .

Quelques raisons pour lesquelles l'état d'animation est gênant pour Redux:

  • Les données d'animation changent fréquemment
  • Les actions spécifiques aux animations polluent l'historique des actions dans votre application, ce qui rend difficile l'annulation d'actions de manière significative
  • L'arbre d'état Redux commence à refléter l'arborescence des composants s'il y a de nombreuses animations spécifiques aux composants
  • Même les états d'animation de base comme animationStarted ou animationStopped commencent à coupler l'état à votre interface utilisateur.

D'un autre côté, il y a certainement des défis à construire l'état d'animation entièrement en dehors de l'arbre d'état Redux.

Si vous essayez de faire des animations vous-même en manipulant le DOM, vous devez faire attention aux trucs comme ceci:

  • Les diffs virtual-dom ne prendront pas en compte la manipulation de votre animation, comme les styles personnalisés que vous définissez sur le nœud - si vous définissez certains styles sur un nœud DOM, vous devez les supprimer vous-même si vous voulez qu'ils disparaissent
  • Si vous effectuez des animations sur le document lui-même, vous devez être extrêmement conscient des mises à jour de virtual-dom - Vous pouvez démarrer une animation sur un nœud DOM, pour constater que le contenu de ce nœud DOM a été modifié au milieu de l'animation
  • Si vous effectuez des animations sur le document lui-même, vous devez vous méfier des styles et des attributs d'écrasement de virtual-dom que vous définissez sur votre nœud, et vous devez vous méfier des styles et des attributs d'écrasement définis par virtual-dom

Et si vous essayez de laisser virtual-dom s'occuper de toute la manipulation du DOM (ce que vous devriez!) Mais sans garder l'état d'animation dans Redux, vous vous retrouvez avec ces problèmes difficiles comme ceux-ci:

  • Comment exposez-vous l'état d'animation à vos composants? État local dans vos composants? Un autre état mondial?
  • Normalement, votre logique d'état est dans les réducteurs Redux, mais vous devez maintenant ajouter beaucoup de logique de rendu directement dans vos composants pour savoir comment ils s'animeront en réponse à votre état. Cela peut être assez difficile et détaillé.

Il existe actuellement des projets géniaux comme react-motion qui font de grands progrès dans la résolution de ce problème _for React_, mais Redux n'est pas exclusif à React. Je pense que cela ne devrait pas non plus être le cas - beaucoup de gens apportent leur propre couche de vue et essaient de s'intégrer à Redux.

Pour tous ceux qui sont curieux, la meilleure solution que j'ai trouvée pour Redux + virtual-dom est de conserver deux atomes d'état: Redux conserve l'état de l'application de base, détient la logique de base pour manipuler cet état en réponse à des actions, etc. L'autre état est un objet mutable qui contient l'état d'animation (j'utilise mobservable). L'astuce consiste alors à souscrire aux changements d'état de Redux _ et aux changements d'état d'animation, et à rendre l'interface utilisateur en fonction des deux:

/* Patch h for jsx/vdom to convert <App /> to App() */
import h from './h'
import { diff, patch, create } from 'virtual-dom'
import { createStore } from 'redux'
import { observable, autorun } from 'mobservable'
import TWEEN from 'tween.js'
import rootReducer from './reducers'
import { addCard } from './actions'
import App from './containers/App'

// Redux state
const store = createStore()

// Create vdom tree
let tree = render(store.getState())
let rootNode = create(tree)
document.body.appendChild(rootNode)

// Animation observable
let animationState = observable({
  opacity: 0
})

// Update document when Redux state 
store.subscribe(function () {
  // ... anything you need to do in response to Redux state changes
  update()
})

// Update document when animation state changes
autorun(update)

// Perform document update with current state
function update () {
  const state = store.getState()
  let newTree = render(state, animationState)
  let patches = diff(tree, newTree)
  rootNode = patch(rootNode, patches)
  tree = newTree
}

// UI is a function of current state (and animation!)
function render (state, animation = {}) {
  return (
    <App {...state} animation={animationState} />
  )
}

// Do some animations
function animationLoop (time) {
  window.requestAnimationFrame(animationLoop)
  TWEEN.update(time)
}
animationLoop()

new TWEEN.Tween(animationState)
      .to({ opacity: 100 }, 300)
      .start()

// Or when you dispatch an action, also kick off some animation changes...
store.dispatch(addCard())
/* etc... */

Merci à tous pour vos bonnes réponses! Laissez-les venir.

Une chose à noter est que nous n'avons pas l'intention d'utiliser Redux pour l'état _all_. Tout ce qui semble important pour l'application. Je dirais que les entrées et l'état d'animation devraient être gérés par React (ou une autre abstraction d'état éphémère). Redux fonctionne mieux pour des choses comme les données extraites et les modèles modifiés localement.

@taggartbg

J'ai trouvé difficile d'utiliser l'arborescence d'état unique de redux lorsque je devais gérer les données de plusieurs sessions. J'avais un nombre arbitraire de branches avec une forme identique et chacune avait plusieurs identifiants uniques (en fonction de l'origine des données, vous utiliseriez une clé différente). La simplicité des fonctions de réduction et de resélection a rapidement disparu face à ces exigences - avoir à écrire des getters / setters personnalisés pour cibler une branche spécifique semblait trop complexe et plat dans un environnement par ailleurs simple et compossible.

Pourriez-vous créer un problème décrivant votre cas d'utilisation plus en détail? Il se peut qu'il existe un moyen simple d'organiser différemment la forme de l'état qui vous manque. En général, plusieurs branches avec la même forme d'état mais gérées par différents réducteurs est un anti-motif.

@istarkov

Augmentation de la probabilité de collisions de clés d'état entre les réducteurs.

Pourriez-vous expliquer plus en détail comment cela se produit? Normalement, nous vous suggérons de n'exécuter qu'un seul réducteur sur n'importe quelle tranche d'état. Comment des collisions peuvent-elles se produire? Faites-vous plusieurs passages sur l'état? Si oui, pourquoi?

@gaearon @istarkov : ce qui signifie peut-être que divers plugins et bibliothèques associées pourraient se bousculer pour le même espace de noms de premier niveau? La bibliothèque A veut une clé supérieure nommée "myState", mais la bibliothèque B, etc.

Ouais, c'est un bon point même si ce n'est pas ce que voulait dire

@gaearon

Pourriez-vous créer un problème décrivant votre cas d'utilisation plus en détail? Il se peut qu'il existe un moyen simple d'organiser différemment la forme de l'état qui vous manque. En général, plusieurs branches avec la même forme d'état mais gérées par différents réducteurs est un anti-motif.

Je serais content de! Je le ferai quand j'aurai un moment.

Je pense que c'est définitivement considéré comme un anti-modèle, même si j'ai un seul réducteur partagé pour gérer ces branches. Néanmoins, je pense que le paradigme fourni par redux n'est pas loin d'être un cas d'utilisation parfait pour sérialiser / désérialiser des branches identiques en tant qu'état immuable. Je ne vois pas pourquoi il serait contraire à la philosophie de redux de construire quelque chose comme reselect avec une logique supposée pour cibler des branches spécifiques par une clé.

Je serais heureux d'en discuter hors fil, je pourrais très bien me tromper.

@taggartbg : parler entièrement sans savoir à quoi ressemble votre code ici. Parlez-vous d'essayer de gérer un état qui ressemble à ceci?

{ groupedData : { first : {a : 1, b : 2}, second : {a : 3, b : 4}, third : {a : 5, b, 6} }

On dirait que vous pourriez gérer cela du côté du réducteur en ayant une seule fonction de réduction qui prend la clé d'ID par groupe dans le cadre de chaque action. En fait, avez-vous regardé quelque chose comme https://github.com/erikras/multireducer , https://github.com/lapanoid/redux-delegator , https://github.com/reducks/redux-multiplex , ou https://github.com/dexbol/redux-register?

De plus, du côté de la resélection, le fait que React-Redux prenne désormais en charge les sélecteurs par composant peut aider, car cela améliore les scénarios dans lesquels vous effectuez une sélection basée sur des accessoires de composant.

@gaearon

Une chose à noter est que nous n'avons pas l'intention que Redux soit utilisé pour tous les états. Tout ce qui semble important pour l'application. Je dirais que les entrées et l'état d'animation devraient être gérés par React (ou une autre abstraction d'état éphémère). Redux fonctionne mieux pour des choses comme les données extraites et les modèles modifiés localement.

Un développeur React lisant la documentation et le didacticiel a déjà cette abstraction d'état éphémère à sa disposition, il est donc possible que cela soit déjà à l'esprit lorsque Redux a du sens pour un projet.

Cependant, du point de vue d'un développeur avec peu ou pas d'expérience avec React qui souhaite adopter une approche fonctionnelle de l'interface utilisateur, il peut ne pas être évident de savoir quel état appartient à Redux. J'ai fait plusieurs projets maintenant où au début Redux et virtual-dom étaient suffisants, mais à mesure que l'application devenait de plus en plus complexe, cette autre "abstraction d'état éphémère" devenait nécessaire. Ce n'est pas toujours évident la première fois que vous ajoutez un réducteur d'animation avec des indicateurs d'animation de base, mais plus tard, cela devient assez pénible.

Il serait peut-être intéressant que la documentation mentionne quel état convient à Redux et quel état est mieux résolu par d'autres outils. Cela peut être redondant pour les développeurs React, mais pourrait être très bénéfique pour les autres développeurs lorsqu'ils examinent Redux et planifient leur architecture d'application.

EDIT: le titre du numéro est "Quels sont les inconvénients de stocker tout votre état dans un seul atome immuable?" Ce "tout votre état dans un seul atome immuable" est exactement le genre de libellé qui finit par obtenir une grande partie du mauvais type d'état dans l'arbre Redux. La documentation pourrait fournir des exemples explicites pour aider les développeurs à éviter ce genre de piège.

@gaearon Nous avons eu une conversation intéressante sur Cycle.js sur les différences architecturales entre un état d'atome unique et le pipping. ICI .

Je sais que ce n'est pas 100% lié à Redux, mais je voulais donner une perspective différente d'une implémentation qui utilise des flux observables comme flux de données autour d'une application (pour Cycle, l'application étant un ensemble de fonctions pures).

Parce que tout dans Cycle est un observable, j'avais des difficultés à faire passer l'état d'un changement d'itinéraire à un autre. Cela était dû au fait que l'état Observable n'avait pas d'abonné une fois qu'une route a été modifiée et a pensé pourquoi ne pas implémenter un seul état d'atome, donc chaque fois que l'état était changé dans mon application, il ferait un rapport au magasin de niveau supérieur, en contournant toute tuyauterie ( vue terrible dessins ici ).

Avec Cycle, parce que les effets secondaires se produisent dans les pilotes, vous devez généralement faire cette boucle du pilote de niveau supérieur au composant et inversement, donc je voulais simplement sauter cela et revenir directement aux composants à l'écoute. Prenait Redux comme source d'inspiration.

Ce n'est pas un cas qui soit vrai ou faux, mais maintenant je fais du piping et nous avons trouvé un pilote d'état, avoir la capacité de diriger mon état vers différents composants selon mes besoins est vraiment flexible pour le refactoring, le prototypage et une solide compréhension chaque source d'État, chaque consommateur et comment ils se sont rencontrés.

Aussi les nouveaux arrivants dans l'application (s'ils comprennent Cycle bien sûr), peuvent facilement relier les points et très rapidement dessiner une représentation visuelle dans l'esprit de la façon dont l'état est géré et diffusé et tout cela à partir du code.

Désolé d'avance si cela est totalement hors contexte, je voulais montrer comment un paradigme différent avait une conversation similaire: smiley:

@timdorr

Chaque widget devra conserver son propre état sans chevauchement entre les deux. Une équipe produit est probablement affectée à chaque widget, elle peut donc ne pas connaître les effets du code des autres sur l'environnement.

Je pense que c'est mieux quand le widget a son propre état (peut-être même son propre magasin (redux?)) Afin qu'il puisse fonctionner de manière autonome dans n'importe quelle application (mashup-) et ne recevoir que certaines des propriétés. Pensez au widget météo. Il peut récupérer et afficher des données par lui-même et ne recevoir que des propriétés telles que la ville, la hauteur et la largeur. Un autre exemple est le widget Twitter.

Bonne discussion!

Surtout, je trouve peu pratique de combiner plusieurs applications / composants / plugins.

Je ne peux pas simplement prendre un module que je construis et le jeter dans un autre module / application, car je devrai importer séparément ses réducteurs dans le magasin de l'application, et je devrai le mettre sous une clé spécifique que mon module sait. Cela m'empêche également de dupliquer le module si j'utilise @connect , car il se connecte à l'état entier.

Par exemple:

Je construis un messager qui ressemble à iMessage. Il a un état redux de currentConversationId etc. Mon composant Messenger a @connect(state => ({ currentConversationId: state.messenger.currentConversationId })) .

Je souhaite inclure ce Messenger dans une nouvelle application. Je vais devoir import { rootReducer as messengerRootReducer } from 'my-messenger' et l'ajouter à combineReducers({ messenger: messengerRootReducer }) pour que cela fonctionne.

Maintenant, si je veux avoir deux instances <Messenger> dans l'application qui ont des données différentes, je ne peux pas, car je suis lié à state.messenger dans le composant Messenger. Fabricationtravailler contre une tranche spécifique du magasin me fera utiliser un @connect .

De plus, disons que l'application contenant a un certain nom d'action qui existe sur le module my-messenger , il va y avoir une collision. C'est pourquoi la plupart des plugins redux ont des préfixes comme @@router/UPDATE_LOCATION .

Quelques idées aléatoires / folles:

1

@connect peut se connecter à une tranche spécifique du magasin, donc je peux inclure dans mon application plusieurs <Messenger> s qui se connectent à leur propre tranche de données, comme state.messenger[0] / state.messenger[1] . Cela devrait être transparent pour <Messenger> qui continue de se connecter à state.messenger , cependant, l'application contenant peut fournir la tranche par quelque chose comme:

@connect(state => ({ messengers: state.messengers }))
class App extends Component {
  render() {
    return (
      <div>
        {this.props.messengers.map(messenger =>
          <ProvideSlice slice={{messenger: messenger}}><Messenger /></ProvideSlice>
        }
      </div>
    )
  }
}

Il y a un problème lors de l'utilisation d'entités normalisées globales, state.entities devra également être présent, donc avec l'état découpé, l'état entier doit être présent d'une manière ou d'une autre. ProvideSlice peut définir un contexte dans lequel les Messenger de @connect pourront lire.

Un autre problème est de savoir comment lier les actions déclenchées par des composants dans <Messenger> n'affectent que cette tranche. Peut-être avoir @connect sous ProvideSlice exécuter mapDispatchToProps actions uniquement sur cette tranche de l'état.

2

Combinez les définitions store et reducer , afin que chaque réducteur puisse être également autonome. Stocker les sons comme un contrôleur tandis qu'un réducteur est une vue. Si nous adoptons l'approche de react, "tout est un composant" (contrairement au contrôleur / directive angular 1.x), cela peut permettre aux composants et à leur état / actions d'être réellement autonomes. Pour des choses comme les entités normalisées (c'est-à-dire «l'état global»), quelque chose comme le contexte de React peut être utilisé, et il peut être accessible à travers toutes les actions (par exemple getState dans thunk ) / composants connectés.

@elado L'idée la plus intelligente que j'ai vue pour le moment est de https://medium.com/@timbur/react -automatic-redux-providers-and-replicators-c4e35a39f1

Merci @sompylasar. Je suis un peu en voyage sur la route, mais quand j'en ai l'occasion, je prévois d'écrire un autre article décrivant plus succinctement les fournisseurs pour ceux qui sont déjà familiers avec React et Redux. Toute personne déjà familière devrait trouver que c'est fou simple / facile. :)

@gaearon aimerait toujours savoir ce que vous en

Ma plus grande bête noire est qu'il est plus difficile de composer, réutiliser, imbriquer et généralement déplacer les composants du conteneur car il existe deux hiérarchies indépendantes en même temps (vues et réducteurs). Il n'est pas non plus tout à fait clair comment écrire des composants réutilisables qui utilisent Redux comme détail d'implémentation ou qui souhaitent fournir une interface conviviale Redux. (Il existe différentes approches.) Je ne suis pas non plus impressionné par le fait que chaque action doive aller «à fond» vers le haut au lieu de court-circuiter quelque part. En d'autres termes, j'aimerais voir quelque chose comme le modèle d'état local React mais soutenu par des réducteurs, et j'aimerais qu'il soit très pratique et adapté à des cas d'utilisation réels plutôt qu'à une belle abstraction.

@gaearon des idées sur la façon de commencer à mettre en œuvre un tel système?

@gaearon

En d'autres termes, j'aimerais voir quelque chose comme le modèle d'état local React mais soutenu par des réducteurs

Je pense que la partie délicate est que l'état local de React est lié au cycle de vie d'un composant, donc si vous essayez de modéliser l'état local dans Redux, vous avez un compte pour le «montage» et le «démontage». Elm n'a pas ce problème car 1) les composants n'ont pas de cycle de vie, et 2) le "montage" d'une vue est dérivé du modèle, alors que dans React, l'état local n'existe qu'après_ une vue a été montée. (modifier: plus précis à dire juste avant qu'il ne soit monté, mais après que la décision de monter a déjà été prise)

Mais comme vous y faites allusion, l'architecture Elm - allant «à fond» vers le haut - a ses propres inconvénients et ne correspond pas très bien au cycle de mise à jour et de réconciliation de React (par exemple, elle nécessite une utilisation libérale des optimisations shouldComponentUpdate() ... qui cassent si vous "transférez" naïvement une méthode de répartition dans render() .)

À un certain moment, j'ai envie d'utiliser des réducteurs + setState et de l'appeler un jour: D À moins que / jusqu'à ce que React découvre une histoire pour externaliser l'arborescence d'état ... même si c'est peut-être ce dont nous discutons vraiment ici

Je suppose que c'est ce que @threepointone essaie de résoudre avec https://github.com/threepointone/redux-react-local

@acdlite le modèle conceptuel d'Elm fonctionne très bien pour l'état local, mais il semble vraiment difficile à utiliser dans des applications du monde réel où nous devons utiliser des bibliothèques, nous concentrer sur le montage, gérer les effets de parallaxe ou autre ... Voir https: // github.com/evancz/elm-architecture-tutorial/issues/49

@slorber Oui, je suis d'accord. N'est-ce pas essentiellement ce que je viens de dire? :RÉ

Je suppose que vos préoccupations sont légèrement différentes. _Si_ (gros "si") vous deviez utiliser l'architecture Elm dans React, cependant, je ne pense pas que vous auriez les problèmes que vous mentionnez car nous aurions toujours accès aux cycles de vie, setState comme trappe de secours, etc.

Oui nous sommes sur la même page @acdlite
L'architecture Elm avec React résoudrait ce problème.
Même Elm pourrait à l'avenir, car il pourrait autoriser l'utilisation de hooks vdom.

Cependant, l'architecture Elm nécessite un peu de passe-partout. Je pense que ce n'est pas vraiment durable à moins d'utiliser Flow ou Typescript pour les actions d'encapsulation / déballage, et il serait préférable d'avoir un moyen plus simple de gérer cet état local, comme la solution de @threepointone

J'ai un autre problème avec l'architecture Elm, c'est que l'action imbriquée fonctionne très bien pour l'état des composants locaux, mais je pense que ce n'est pas une si bonne idée une fois que vous avez besoin d'une communication entre des composants découplés. Si widget1 doit dérouler et introspecter des actions profondément imbriquées pour trouver un événement déclenché par widget2, il y a un problème de couplage. J'ai exprimé quelques réflexions ici et je pense qu'utiliser Elm avec 2 "boîtes aux lettres" pourrait faire le travail. C'est aussi ce que redux-saga peut aider dans les architectures non-orme avec des événements plats.

Je commence à avoir une bonne idée sur la façon de créer des applications redux évolutives et j'essaierai d'écrire quelque chose à ce sujet lorsque je serai moins occupé

@gaearon Parlez -vous de react-redux-provide dans votre dernier message ici? Je pose la question parce que si c'est le cas, d'après votre réponse, il est clair que vous ne l'avez pas suffisamment regardée.

Aussi, en ce qui concerne la mention de externaliser l'arborescence d'état , c'est exactement le problème que les fournisseurs sont censés résoudre.

L'API fournie par le décorateur provide est certainement une abstraction et oui, elle dépend actuellement de redux , mais penser qu'elle dépend de redux est la mauvaise façon de Pensez-y, car il ne s'agit en réalité que d'un peu de "colle" entre les composants et l'arborescence des états, car le context de React n'est pas encore complètement développé. Pensez-y comme une méthode idéale pour manipuler et représenter les arbres d'états via actions et reducers (c'est-à-dire l'état du magasin) comme props , où vous pouvez déclarer essentiellement les actions et reducers comme propTypes (et / ou contextTypes dans le futur) de la même manière que vous import autre chose dans votre application. Lorsque vous faites cela, tout devient incroyablement facile à raisonner. Les context React pourraient potentiellement évoluer pour inclure ces fonctionnalités de base, et dans ce cas, il faudrait 2 secondes entières pour grep et supprimer @provide et import provide from 'react-redux-provide' afin que vous soyez puis laissé avec des composants simples qui n'en dépendent plus. Et si cela n'arrive jamais, ce n'est pas grave car tout continuerait à fonctionner parfaitement et de manière prévisible.

Je pense que vous simplifiez à l'extrême le problème. Vous devez encore résoudre le montage et le démontage.

@acdlite Vous avez un exemple?

Si l'une des choses auxquelles vous faites référence est (par exemple) de récupérer props partir d'une base de données basée sur quelque id ou autre, ceci (parmi presque toutes les autres applications pratiques auxquelles vous pouvez penser) peut être facilement réalisé avec les prestataires. Après avoir terminé d'autres choses sur lesquelles je travaille, je serai heureux de vous montrer à quel point c'est facile. :)

Peut être intéressant: implémentations d'état réactif comparées .
Elm, Redux, CycleJS ... (travaux en cours)

Un autre besoin possible pour plusieurs magasins est lorsque plusieurs _lignes de temps_, _les voyages dans le temps_, enregistrer des emplacements et enregistrer la fréquence sont possibles et nécessaires. Par exemple, user data et user actions qui sont spécifiques à un seul utilisateur et common data et group actions spécifiques à un groupe d'utilisateurs (par exemple, projet multi-utilisateurs partagé ou document). Cela facilite la séparation de ce qui peut être annulé (modifications des données personnelles) de ce qui est fixe (modifications déjà augmentées par d'autres utilisateurs) et facilite également les cycles de sauvegarde et / ou de synchronisation différents.

Nous sommes dans la phase de conception / développement précoce d'une application angulaire 1.x avec un état d'esprit de développement angulaire 2.0 et pensant utiliser redux. Dans le passé, nous avons utilisé le flux avec plusieurs magasins répondant à certaines dépêches courantes et à certaines dépêches dédiées.

Redux me semble bien, mais je ne suis pas en mesure de raisonner le concept de magasin unique pour l'ensemble de l'application. Comme d'autres l'ont également souligné ci-dessus, nous avons un certain nombre de gros widgets qui sont censés être hébergés sur différentes applications grand public et qui ont besoin de magasins de données dédiés pour chacun d'entre eux.

Même si les réducteurs peuvent être divisés et peuvent vivre avec la fonctionnalité / le widget qui l'utilise, nous devons tous les regrouper lorsqu'il est temps de créer le magasin. Nous aimerions voir nos widgets exister sans avoir aucune référence directe / indirecte à des réducteurs dont il ne se soucie pas.

Nous attendons vraiment avec impatience des conseils sous forme de documentation ou un commentaire pour décrire les meilleures pratiques dans de telles situations avant de commencer à utiliser redux dans notre application. Toute aide sera vraiment appréciée. Merci.

@VivekPMenon : quelles sont vos préoccupations spécifiques? Taille de l'arborescence d'état? Être capable de nommer les actions / garder les réducteurs isolés? Possibilité d'ajouter ou de supprimer dynamiquement des réducteurs?

Pour ce que ça vaut, je viens de terminer une liste de liens cataloguant l'écosystème des addons Redux. Il existe un certain nombre de bibliothèques qui tentent de résoudre certains de ces concepts d'isolement / duplication / d'étendue. Vous voudrez peut-être jeter un œil à la page du page de l'état local / du composant pour voir si l'une de ces bibliothèques pourrait vous aider dans vos cas d'utilisation.

@markerikson. Merci beaucoup pour la réponse. Ma confusion est principalement autour du 2ème point (Isolation). Supposons que l'une de nos équipes crée le widget A basé sur le flux et doit utiliser le magasin pour gérer son état. Supposons qu'ils créeront un package jspm / npm et le distribueront. Il en va de même avec une autre équipe qui crée le widget B. Ce sont des widgets / packages indépendants et n'ont pas besoin d'avoir une dépendance directe / indirecte les uns avec les autres.

Dans ce contexte, dans un monde à flux régulier. Les widgets A et B ont leurs propres magasins dans leur package (ils peuvent écouter certaines actions courantes, mais supposent qu'ils ont plus d'actions qui ne sont pas courantes). Et les deux widgets dépendront du répartiteur global. Dans le modèle redux, où ce magasin serait-il créé (physiquement). Les composants d'interface utilisateur des widgets A et B doivent tous deux utiliser l'instance de stockage pour écouter les modifications et obtenir l'état. En flux régulier, ils opèrent sur leur propre instance de magasin. J'essaie de visualiser comment le concept de magasin unique va jouer avec cette situation.

@VivekPMenon c'est une question valable que certains d'entre nous essaient de résoudre.

Vous pouvez obtenir des informations ici: https://github.com/slorber/scalable-frontend-with-elm-or-redux

Jetez également un œil à ma réponse sur Redux-saga ici: http://stackoverflow.com/a/34623840/82609

@slorber Merci beaucoup pour les conseils. Dans l'attente des idées à venir ici https://github.com/slorber/scalable-frontend-with-elm-or-redux

@taggartbg Avez-vous finalement ouvert un problème? Je suis intéressé par une chose similaire - lorsque j'ai un react-router avec différents sous-arbres indépendants, je trouve problématique de conserver l'état de la route précédente tout en naviguant vers une nouvelle branche.

@slorber

Permettez-moi de commencer par dire que la saga redux est un projet merveilleux avec beaucoup de ressources et cela n'essaie en aucun cas de diminuer cela.

Mais je ne l'utiliserais dans aucun de mes projets dans son état actuel. Voici quelques raisons pour lesquelles

Les Sagas sont apatrides

Vous ne pouvez pas sérialiser l'état d'un générateur, ils ont du sens pendant qu'ils fonctionnent mais ils sont complètement liés à l'état de la saga. Cela entraîne une série de limitations:

  • Vous ne pouvez pas les générateurs de voyage dans le temps
  • Vous ne pouvez pas tracer le chemin qui a conduit à l'état actuel d'une saga
  • Vous ne pouvez pas définir un état initial
  • Vous ne pouvez pas séparer la logique métier de l'état de l'application [*]
  • Vous ne pouvez pas stocker de sagas

[*] Cela pourrait être interprété comme définissant la logique métier dans la définition du modèle de l'application, faisant du M et du C dans MVC la même chose, ce qui pourrait être considéré comme un anti-modèle

La répartition des actions n'est pas déterministe

L'un des grands avantages de l'utilisation de redux est de garder une compréhension simple de la réaction de l'État à une action. Mais redux-saga rend les actions sujettes à des résultats inattendus car vous ne pouvez pas dire dans quelle partie de la saga vous partez.

État sérialisable

Le fait est que l'état actuel de la saga a un impact sur l'état actuel de l'application, et les gens insistent sur le fait de garder l'état sérialisable, mais comment traduisez-vous cela en sagas? Si vous ne pouvez pas sérialiser complètement l'état, vous ne le sérialisez pas vraiment. Vous ne pouvez fondamentalement pas récupérer l'état d'une application sans récupérer la saga également.


Stocker la logique dans l'état est mauvais, et à mon avis, c'est ce que fait redux saga d'une certaine manière.

Il est parfaitement normal de supposer que l'état de votre application est spécifique à _votre_ application. Cela signifie qu'il est également acceptable de supposer que l'état de l'application est prévisible tant qu'il fonctionne au-dessus de _votre_ application. Cela signifie qu'il est OK pour conserver les informations du magasin qui déterminent le comportement de votre application sans y enregistrer réellement la logique.

@eloytoro

Je comprends vos inquiétudes. Si vous regardez les problèmes de redux-saga, vous verrez que ces choses sont discutées et qu'il y a des solutions à trouver.

Notez également que redux-saga choisit de mettre en œuvre avec des générateurs, mais ce n'est pas du tout une exigence pour implémenter le modèle de saga, et il y a des avantages et des inconvénients.

Il y a un effet de sélection qui vous permet d'utiliser l'état redux dans votre redux-saga, donc si vous ne voulez pas cacher l'état dans les générateurs, vous pouvez simplement placer votre état de saga directement dans redux-store, mais cela implique plus de travail.

Il existe des outils de développement de saga qui permettent de retracer l'exécution des sagas.

Il existe des solutions possibles pour le voyage dans le temps mais pas encore de mise en œuvre concrète

Droite. Un sujet assez long et très intéressant. J'ai comparé différentes approches de gestion de l'État.

Je garde quelques points à l'esprit en comparant:

  1. source unique de vérité
  2. à quel point il est facile / difficile d'effectuer un rendu côté serveur et d'alimenter les données initiales côté client
  3. devtools comme annuler / rétablir et le stockage de l'historique persistant après les recharges.
  4. animations, animations, animations. Je me demande toujours _ "comment les animations fonctionneraient avec la solution a ou b" _. C'est une clé pour moi car les produits que je développe sont centrés sur l'UX. Je partage une grande partie des confusions de @jsonnull .
  5. réutilisation de composants dans une autre application
  6. hydratation des données référencées (_major pain_) afin de conserver l'intégrité (liée à l'alimentation des données initiales du côté serveur ou de toute autre source de données)
  7. communauté d'autres développeurs pour une discussion comme celle-ci

Ce sont donc des points dont je me méfie lorsque je pense à la gestion de l'État. Redux est le gagnant en cochant la plupart des cases. Je connais assez bien Redux et ce sont les premières choses qui m'ont fait respirer calmement dans le domaine des js côté client.

Les autres projets les plus notables que j'ai examinés sont le modèle SAM (_ difficile d'entrer dans l'état d'esprit et pas d'outils_) et Mobx (_connu pour son stockage modifiable_).

Les deux partagent l'idée d'une source unique de vérité et je pense qu'il est intéressant de voir comment ils la gèrent.

Mes recherches (limitées bien sûr) sur Mobx aboutissent à ceci:

  1. Mobx (tout comme Redux) préconise une source unique de vérité mais en tant que classe avec des propriétés observables. Ce concept crée les différences avec le redux et le flux dans son ensemble:

    1. Mobx préconise également les sous-magasins (_nested / part of the global single one_). Pour moi, cela ressemble beaucoup à la définition d'un schéma côté client. C'est un peu ennuyeux car vous avez probablement également un schéma sur le serveur. Je vois des similitudes avec redux-orm . AFAIK les sous-magasins sont destinés à être passés aux composants en tant qu'accessoires (de cette façon, le composant ne repose pas sur le mappage de l'état aux accessoires ou à certaines clés de l'arborescence globale). Cependant, passer une seule propriété brise mon idée des «propriétés» - elles devraient être descriptives de ce dont le composant a besoin. Cela me fait penser que ce serait un problème si React propTypes peut être utilisé pour définir la forme du magasin passé. Aussi: avoir des magasins et des sous-magasins comme classes permet de référencer les données directement sans avoir besoin de normalisation, mais c'est un peu inutile si vous voulez alimenter les données initiales à cause de:

    2. L'idée de stocker en tant que classe signifie cependant que le développeur n'a pas la sérialisation prête à l'emploi, ce qui signifie que c'est votre travail à faire si vous souhaitez alimenter les données initiales.

  2. L'historique et l'annulation / la restauration peuvent être réalisés en prenant des instantanés de l'état après sa modification. Mais vous devez écrire vous-même le sérialiseur, le désérialiseur. Un exemple est donné ici: https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/time.js
  3. L'avantage d'écrire des sérialiseurs est que vous pouvez laisser des éléments de côté. cela semble être un bon endroit pour stocker des données non persistantes liées aux animations. Donc, si vous le sérialisez, vous l'ignorez simplement et vous n'avez pas de restauration / annulation pleine d'étapes d'animation.
  4. Mobx préconise de conserver les dérivations des données dans le magasin (en tant que méthodes du magasin) par opposition au décorateur reselect + connect . Cela me semble un peu faux.
  5. En raison de sa nature observable, mobx facilite la définition de composants purs efficaces. La comparaison superficielle des propriétés n'est pas un problème car le contenu est de toute façon observable.

En comparant SAM et Redux, j'aime réfléchir à la façon dont la logique s'écoule:

  1. Redux:

    1. Définir un magasin

    2. Définir les fonctions qui gèrent les changements de magasin (réducteurs)

    3. Définissez des identificateurs d'événement (action redux) (type d'action redux) pour chaque changement de magasin et exécutez un réducteur spécifique lorsqu'il correspond

    4. Mappez le magasin aux propriétés du conteneur de composants, en passant éventuellement des gestionnaires d'interaction de l'utilisateur (clic, type, sscroll, etc.) en tant que fonctions qui déclenchent simplement une action d'événement / de distribution.

    5. Le composant déclenche un événement (action redux) qui est comparé à tous les réducteurs

    6. Rerender avec le nouvel état.

  2. SAM:

    1. Définissez un magasin.

    2. Définissez une fonction accesseur / régleur pour le magasin. Ce setter accepte toutes les données et uniquement sur la base des données, il essaie de déterminer où les placer dans le magasin et s'il a besoin de demander des données au serveur ou de les y conserver. Il n'est pas toujours possible de savoir quoi faire avec les données simplement en les inspectant, donc parfois des identificateurs / indicateurs sont utilisés (comme isForgottenPassword ). Ce setter est également destiné à valider les données et à stocker les erreurs à la place. Une fois le setter terminé -> Rerender avec le nouvel état.

    3. Définissez des fonctions qui acceptent des données, transformez-les (alternative aux réducteurs redux) et exécutez le setter en passant les données transformées. Ces fonctions ont maintenant une idée de la forme du magasin. Ils ne connaissent que les données qui leur ont été transmises.

    4. Mappez les données du magasin aux propriétés du conteneur de composants, mais il est également obligatoire de transmettre les gestionnaires des gestionnaires d'interaction utilisateur (clic, type, défilement, etc.). Notez que ce sont les fonctions réelles (appelées réducteurs dans redux) et non les fonctions qui exécutent un appel de répartition.

    5. Remarque: il n'y a pas de répartition des actions, passage direct des réducteurs.

Ainsi, tous les redux, SAM et Mobx gardent l'état au même endroit. La façon dont ils le font entraîne leurs propres revers. Aucun ne peut battre redux et son écosystème jusqu'à présent.

Le plus gros inconvénient que je vois est également la gestion de l'état non sérialisable.
Dans mon cas j'ai un plugin API et donc des actions et une logique qui n'a rien à voir avec Redux mais qui a besoin d'interagir avec mon application. Donc, fondamentalement, j'ai besoin de recréer initialement un tas de classes basées sur un état donné.

Je travaille sur quelques concepts pour mieux gérer ce type d'état non sérialisable. Y compris le recréer à partir de n'importe quel état donné. J'ai déjà réussi à déplacer tout sauf les références de fonction / objet dans le magasin. Ce qui reste sont des objets avec leur propre cycle de vie avec une API définie qui interagissent avec mon application et par conséquent avec mes actions. - Se sent similaire aux composants React, mais pas pour le rendu de l'interface utilisateur mais pour la logique personnalisée.

Je ne sais pas si vous avez lu cet article d'Erik Meijer . Juste une simple citation:

Contrairement à la croyance populaire, rendre les variables d'état immuables est loin d'éliminer les effets impératifs implicites inacceptables

Il est peut-être temps de revoir toutes ces absurdités d'immuabilité. Tout le monde vous dira que c'est un problème de performances lorsque l'arborescence d'état unique augmente en taille. Le modèle SAM fait de la mutation un citoyen de première classe du modèle de programmation, beaucoup de simplifications se produisent lorsque vous faites cela de ne plus avoir besoin de Sagas, à toutes ces machines à états auxiliaires nécessaires pour suivre les effets (par exemple, la récupération).

L'article ne plaide pas vraiment en votre faveur. Erik Meijer revisite "cette absurdité d'immuabilité" et l' embrasse complètement . Il soutient que l'élimination de la mutation d'état est une première étape fondamentale, fondamentale mais non suffisante. Son objectif ultime est l'élimination complète des effets secondaires implicites, ce qui rendrait toute gestion étatique obsolète (il n'y aurait pas d'état à gérer!).

Je suis fan du travail d'Erik Meijer et je lirai et écouterai avec empressement tout ce qu'il a à dire, tout en gardant à l'esprit qu'il n'opère pas sous les mêmes contraintes commerciales, technologiques et intellectuelles que moi.

le problème clé en génie logiciel est que tout le monde semble croire qu'une affectation est une mutation IS-A. Les langages de programmation ne prennent en charge que les affectations, la mutation est laissée à l'interprétation du programmeur.

L'ajout d'un wrapper de fonction à un tas d'affectations ne va pas devenir automatiquement le meilleur moyen de muter l'état. C'est absurde.

Il y a une théorie derrière (Application) State Mutation, elle s'appelle TLA +, assez étrangement ... Erik ne mentionne jamais TLA + ...

JJ-

Permettez-moi d'élaborer, car il semble y avoir beaucoup de confusion. L'article d'Erik démontre de façon éloquente que les langages de programmation impératifs sont quelque peu cassés en ce qui concerne l'état de mutation. Je pense que tout le monde est d'accord là-dessus.

La question est de savoir comment procéder et y remédier? Ne conviendrez-vous pas que c'est le genre de problème qui nécessite une approche du « premier principe »?

Il y a à peu près un principe unique sur lequel nous pouvons tous nous entendre en informatique, le Dr Lamport le déclare ainsi: «Une grande partie de l'informatique concerne les machines d'État». et il poursuit: "Pourtant, les informaticiens sont tellement concentrés sur les langages utilisés pour décrire le calcul qu'ils ignorent en grande partie que ces langages décrivent tous des machines à états."

En effet tous les langages de programmation nous permettent de spécifier des "machines à états" quelle que soit leur sémantique. Malheureusement, de nombreux langages de programmation sont orientés vers «l'action» plutôt que vers «l'action d'état». C'est pour une bonne raison, il existe une grande classe de problèmes qui sont codés beaucoup plus efficacement avec un formalisme basé sur l'action (où les états de la machine d'état peuvent être largement ignorés). Lorsque vous les ignorez, vous déclarez essentiellement que toutes les actions sont possibles dans un état donné, ce qui, nous le savons tous, n'est généralement pas vrai.

Vous pouvez rire autant que vous le souhaitez de l'exemple haha ​​d'Erik:
// prints Ha var ha = Ha(); var haha = ha+ha;

mais tout ce qu'il déclare, c'est que vous prenez la mauvaise action dans un état donné. Ni plus ni moins.

Qu'apporte la programmation fonctionnelle à la table? pas grand-chose du tout. Il exprime de manière très alambiquée la relation entre l'action et les résultats qu'elle pourrait avoir. C'est un peu comme essayer de lancer une flèche (même une flèche monadique intelligente) dans un labyrinthe géant en espérant que vous atteindrez votre cible.

TLA + apporte une manière beaucoup plus naturelle de gérer les effets, d'abord parce qu'il a une notion claire de ce qu'est une étape et comment la mutation est liée à l'action. Vous êtes libre d'ignorer TLA +, mais c'est un peu comme si vous vous plaigniez d'avoir essayé d'envoyer une fusée sur la lune en supposant que la terre était plate et qu'il était vraiment difficile de diriger le vaisseau spatial. Je sais que nous vivons dans un monde où les gens croient pouvoir plier la réalité, tant que suffisamment de gens croient ce qu'ils disent ... cela ne vous mènera toujours nulle part.

Donc encore une fois, je serais vraiment, vraiment, vraiment curieux de voir ce que pense Erik de TLA +, je me suis connecté avec lui sur LinkedIn et lui ai posé la question. Je n'ai jamais eu de réponse ... Après tout, le Dr Lamport n'a reçu qu'un prix Turing pour cela, cela ne vaut probablement pas son temps.

Honnêtement, @jdubray , vous êtes à la limite de la pêche à la traîne à ce stade. Vous avez répété vos arguments des dizaines de fois dans ce fil, dans d'autres questions et ailleurs. Vous avez insulté Dan et la communauté Redux, vous avez agité vos informations d'identification et continuez à invoquer les mots «TLA +» et «Lamport» comme s'ils étaient le Saint Graal et la réponse à la vie, à l'univers et à tout. Vous avez insisté sur le fait que SAM est de loin supérieur à Redux, mais sans écrire grand-chose comme des applications de comparaison significatives, malgré de nombreuses invitations à montrer des preuves. Vous n'allez vraiment pas changer d'avis quiconque n'est pas déjà convaincu. Je vous suggère honnêtement de passer votre temps à faire quelque chose de plus productif.

Je vais fermer le fil à ce stade. Les commentaires peuvent continuer, je suppose, mais il n'y a certainement rien de concret ici.

@markerikson Je voudrais continuer cette discussion même si elle ne va nulle part, le sujet m'intéresse un peu. Il vaudrait mieux effacer ses commentaires toxiques et poursuivre la bonne discussion open source.
Outre que @antitoxic (sans jeu de mots) a clairement indiqué à quel point l'approche à laquelle il se réfère est utile ou non, nous savons à quel point les autres problèmes se sont révélés graves à cause de ses interventions, donc il ne sert plus à rien d'écouter.

C'est ok Mark, vous pouvez supprimer mes commentaires, pourquoi gâcher une conversation aussi brillante?

@jdubray vous seul le voyez comme brillant. Je pense que nous pensons tous que vous essayez de nous vendre quelque chose sans plus d'arguments que "Lamport a obtenu un prix Turing, alors écoutez-moi, j'ai raison!".

Je ne dis pas TLA + ou quoi que ce soit qui n'est pas bon, et peut-être que dans le futur quelqu'un fera un meilleur travail que vous le vendre. C'est juste la façon dont vous l'expliquez qui ne nous aide pas du tout à comprendre, et votre ton condescendant permanent ne m'invite pas à investir du temps dans l'apprentissage de TLA +.

L'article d'Erik démontre de façon éloquente que les langages de programmation impératifs sont quelque peu cassés en ce qui concerne l'état de mutation. Je pense que tout le monde est d'accord là-dessus.

En fait, vous utilisez toujours des termes comme I believe everyone agrees on that dans tous vos messages. Ce n'est pas quelque chose de bon, utile ou accueillant pour les gens. Nous ne sommes pas tous d'accord avec vous par défaut, et la plupart d'entre nous n'ont pas lu et ne liront pas l'article d'Erik. Si vous ne parvenez pas à convaincre les gens de manière accueillante, sans leur demander de lire des tonnes de papier avant et en les faisant sentir stupides s'ils ne sont pas d'accord ou en désaccord avec vous, cela n'aidera pas à diffuser vos idées.

Vous êtes libre d'ignorer TLA +, mais c'est un peu comme si vous vous plaigniez d'avoir essayé d'envoyer une fusée sur la lune en supposant que la terre était plate et qu'il était vraiment difficile de diriger le vaisseau spatial.

Cela ressemble définitivement à une analogie et certainement pas à un premier principe . Sérieusement, pensez-vous que cette déclaration doit être considérée comme une "discussion brillante"?

Je pense que les discussions sont en fait plus brillantes quand on ne parle pas en elles, car on monopolise toute l'attention sur TLA + et en fait, on empêche les gens non intéressés par TLA + d'essayer d'envoyer leur fusée sur la lune avec quelque chose de plus concret, que la moyenne les gens peuvent comprendre, comme Redux.

Ne dites toujours pas que TLA + ne vaut pas la peine, mais vous en parlez de la mauvaise façon, aux mauvaises personnes. Peut-être que si Erik ne vous a pas répondu, c'est parce qu'il ne vous comprend pas non plus? Peut-être êtes-vous si intelligent que seul Lamport peut vous comprendre? Peut-être que dans le futur vous gagnerez un prix Turing? Je ne sais pas, mais il est certain que la discussion actuelle n'apporte rien à la table.

Peut-être faire comme @gaearon et créer des tutoriels vidéo TLA + / SAM pour egghead. Si vous pouvez expliquer vos idées de manière très simple, en moins de 2h de vidéos, et à la fin vos patrons cliquent et se sentent utiles pour les développeurs, vous gagnez. Redux l'a fait pour beaucoup de gens. Vous avez seulement "essayé" d'expliquer théoriquement pourquoi SAM / TLA + est meilleur que Redux, et vous avez fourni une implémentation TodoMVC affreuse. Nous sommes des chercheurs CS ici, nous avons besoin de matériel concret. Pouvez-vous nous montrer une application, écrite en Redux vs écrite en SAM, où les gens auraient réellement l'impression que l'implémentation SAM est meilleure? Cela n'a pas été le cas jusqu'à présent.

@Niondir J'ai trouvé un moyen d'utiliser des sagas dans les cas où vous avez besoin de récupérer un état.
C'est plus comme un modèle ou un ensemble de règles à suivre

  • Séparez vos sagas afin que chacune d'elles n'ait pas plus d'un seul put . Si vous avez des sagas avec plus de put , divisez-les et enchaînez-les avec un effet call . Par exemple
function* mySaga() {
  yield put(action1());
  yield put(action2());
}

// split into

function* mySaga() {
  yield put(action1());
  yield call(myNextSaga);
}

function* myNextSaga() {
  yield put(action2());
}
  • Les sagas avec des effets take doivent être divisées dans la logique qui suit le take et le point de départ de la saga. Par exemple
function* mySaga() {
  yield take(ACTION);
  // logic
}

// split it into

function* rootSaga() {
  yield takeLatest(ACTION, mySaga);
}

function* mySaga() {
  // logic
}
  • Pour chaque saga, créez une sorte de fonction de "point de contrôle", ce qui signifie qu'à l'init, lisez l'état et à partir de là call la saga dont vous avez besoin
function* rootSaga() {
  // emit all forks and take effects that need to run in the background
  yield call(recoverCheckpoint);
}

function* recoverCheckpoint() {
  const state = yield select();
  if (state.isFetching) {
    // run the saga that left the state like this
  }
}

L'explication pour laquelle cela fonctionnerait est basée sur un ensemble d'hypothèses qui sont généralement vraies

  • take effets
  • Ne pas autoriser plus d'un appel put par saga permet des changements atomiques dans l'état, de la même manière qu'une action ne peut modifier l'état que d'une seule manière, une saga est également limitée à cette capacité, ce qui crée en quelque sorte une corrélation entre les sagas et la répartition des actions.

Diviser les sagas de cette façon a également des _effets_ positifs

  • Les sagas sont plus réutilisables
  • Les sagas sont plus facilement modifiés
  • Les Sagas sont plus faciles à comprendre

mais @slorber :

Beaucoup de gens trouvent cet exemple de "jeu de pêche" assez intéressant à coder car il illustre de première main comment SAM fonctionne dans son intégralité, y compris une représentation d'état dynamique et le "prédicat d'action suivante". NAP réduit le besoin de Sagas et ne nécessite pas de rompre le principe Redux n ° 1 qui est un arbre à états unique, mais que sais-je?

Parfois, lorsque j'utilise Ableton Live, la pensée me vient à l'esprit "serait-il possible d'écrire l'interface graphique Ableton Live comme une application React / Redux?" Je pense que la réponse est non, il serait tout simplement trop difficile d'obtenir des performances acceptables. Il y a une raison pour laquelle il est écrit en Qt et une raison pour laquelle Qt a une architecture de signaux / slots.

J'utilise React / Redux avec Meteor. Je stocke les documents mongo dans l'état Immutable.js en distribuant des actions Redux pour les rappels à Mongo.Collection.observeChanges . J'ai récemment découvert que lors de l'abonnement à plusieurs milliers de documents, l'interface utilisateur était extrêmement lente au démarrage car des milliers d'actions Redux étaient envoyées lorsque Meteor envoyait les résultats de l'abonnement initial un par un, provoquant des milliers d'opérations Immutable.js et des milliers de rendus aussi rapidement que possible. Je suppose que RethinkDB fonctionne également de cette façon, même si je ne suis pas sûr.

Ma solution était de créer un middleware spécial qui mettrait de côté ces actions de collecte, puis les distribuerait par lots à un rythme limité, afin que je puisse ajouter ~ 800 documents en un seul changement d'état. Cela a résolu les problèmes de performances, mais la modification de l'ordre des événements est intrinsèquement risquée car elle peut conduire à un état incohérent. Par exemple, je devais m'assurer que les actions de statut d'abonnement étaient distribuées via le même throttler, afin que les abonnements ne soient pas marqués comme prêts avant que tous les documents aient été ajoutés à l'état Redux.

@ jedwards1211 J'ai eu un problème similaire (beaucoup de petits messages qui composaient les données initiales).

Résolu de la même manière en effectuant une mise à jour par lots. Au lieu d'un middleware, j'ai utilisé un (petit) mécanisme de file d'attente personnalisé (pousser toutes les mises à jour dans un tableau, et à intervalles réguliers s'il y a quelque chose à mettre à jour, les mettre à jour toutes en même temps.

@andreieftimie ouais, c'est plus compliqué dans mon application car certaines interactions de l'interface utilisateur nécessitent des mises à jour immédiates, comme le glissement et le zoom des tracés. Je dois donc disposer d'une couche middleware pour décider si je souhaite envoyer une action immédiatement ou la mettre dans une file d'attente pour une répartition par lots.

Pour ce que ça vaut, j'ai eu l'idée de créer des "branches". Cela défie totalement le principe du magasin unique, mais cela semblait être un bon compromis.

https://github.com/stephenbunch/redux-branch

@ jedwards1211 Je pense qu'au fur et à mesure que les applications deviennent de plus en plus complexes, il est finalement nécessaire de casser certains types de données et certains types de mises à jour afin que certains magasins ne gèrent pas la charge complète.

Disons qu'il y a un état de base que vous souhaitez synchroniser avec le backend, il y a un état d'animation pour que les composants de l'interface utilisateur de votre application s'affichent correctement en fonction des actions en cours effectuées, et peut-être que vous avez des données de débogage que vous suivez séparément.

Dans cet exemple artificiel, il est certainement possible de construire tout cela à partir d'un seul magasin Redux, mais en fonction de la façon dont vous l'architecte, il y aura des lignes floues entre les préoccupations de diverses branches de l'arborescence d'état, et peut-être un manque de clarté concernant l'état avec lequel une action donnée est destinée à interagir.

Je pense qu'il est parfaitement raisonnable d'utiliser différentes stratégies de gestion d'état en fonction de la façon dont vous voulez que l'état soit défini et des caractéristiques de performance que vous en attendez. Redux est convenablement adapté à l'état dans lequel vous voudrez peut-être revenir en arrière ou rejouer des actions, ou sérialiser et revenir plus tard. Pour l'état d'animation, vous pouvez utiliser un magasin mutable ou un magasin réactif si vous le souhaitez. Il y aura moins de frais généraux de cette façon et vous ne polluerez pas l'état de votre application avec cet état d'interaction transitoire (mais toujours nécessaire).

Très vite, je veux faire un point en utilisant la prochaine architecture de fibre React. Voici un lien , excuses si ma compréhension est un peu dépassée. En gros, l'architecture fibre reconnaît qu'il existe différents types de mises à jour qui se propageront à travers une arborescence de composants React, et il y a des attentes différentes concernant le moment et la manière dont ces mises à jour seront exécutées. Vous voulez que les mises à jour d'animation soient rapides, réactives et fiables, et vous ne voulez pas que de grandes mises à jour d'interaction introduisent du jank dans vos animations et autres.

Ainsi, l'architecture fibre décompose les mises à jour en paquets de travail et les planifie selon une priorité basée sur le travail effectué. Les animations ont une priorité élevée, de sorte qu'elles ne sont pas interrompues, et les mises à jour mécaniques plus lentes ont une priorité inférieure.

J'ai sauté de nombreux détails sur les fibres React et je me suis probablement trompé dans le processus, mais ce que je veux dire, c'est que c'est le type d'approche granulaire que je pense nécessaire pour votre magasin de données avec différents types de données.

Si je construisais une application complexe aujourd'hui, je commencerais par une classe de magasin de premier niveau de type Redux soutenue par quelques magasins différents. Grossièrement:

  • Le magasin de données de niveau supérieur a une file d'attente pour les actions entrantes
  • Les actions sont destinées soit aux actions d'animation qui devraient être distribuées rapidement, soit aux interactions d'application qui ont une priorité inférieure
  • Les actions d'animation dans la file d'attente sont distribuées à un backend observable à partir d'une boucle requestAnimationFrame
  • Les actions d'interaction sont envoyées vers redux lorsque les actions d'animation de la file d'attente sont terminées

Cette histoire semble assez proche d'une solution d'état complète basée sur Redux. Redux est adapté aux données que vous souhaitez afficher après une séquence d'actions, et il existe de nombreux autres états qui nécessitent également une manipulation minutieuse.

@jsonnull Je n'ai jamais eu besoin de stocker l'état d'animation dans un magasin - l'état local a toujours été idéal pour mes cas d'utilisation d'animation. Je serais curieux de savoir s'il existe des cas d'utilisation pour lesquels l'état d'animation local est totalement inadapté. On dirait cependant qu'ils ont de bonnes idées pour la nouvelle architecture.

@ jedwards1211 Il y a quelques cas

Dans un cas, vous utilisez une bibliothèque autre que Redux où vous n'avez pas d'état local. (Ok, oui, je sais que je triche un peu ici.) Si vous utilisez une approche hyperscript très légère, pas de dom virtuel, des composants fonctionnels purs, alors au lieu de l'état local, vous devrez passer une animation state sur le nœud racine ou sur le nœud que vous restaurez.

Dans ce cas, avoir une arborescence d'états avec quelques détails d'animation définis vous permettra de contourner le manque d'état local afin de pouvoir déclencher des animations, faire exécuter les animations pendant une durée, etc.

L'essentiel ici est qu'il y a deux façons de faire ces animations: faites-les dans le cadre de votre rendu et gardez la métaphore «UI en fonction de l'état» intacte, ou modifiez le DOM séparément. Presque toujours, la meilleure réponse est de simplement refaire le rendu au lieu de muter.


Maintenant, pour un exemple où vous avez la possibilité de conserver un état d'animation local, en créant un jeu par navigateur avec une interface utilisateur basée sur React.

  • Habituellement, vous conduisez le jeu en utilisant des ticks ... le temps de jeu commence à 0 et est incrémenté à chaque image. Les animations de jeu de base peuvent être effectuées sur un canevas ou dans WebGL où il n'y a pas d'état local, vous allez donc baser l'heure de début et la progression de l'animation en fonction de ces graduations.

Ex: Un personnage de sprite a une animation comprenant 10 images, et vous voulez jouer l'animation sur ~ 400 ms, vous allez donc changer le sprite dessiné toutes les 2 ticks environ.

Vos graduations peuvent également être des horodatages si vous souhaitez une résolution plus élevée.

  • Votre jeu peut également se mettre en pause, auquel cas vous voudrez peut-être arrêter certaines animations effectuées dans le côté de l'interface utilisateur de React.

Dans cet exemple de jeu, ce que vous ne voulez pas faire est d'incrémenter vos graduations dans le cadre d'une action tick ... à la place, vous voulez incrémenter les graduations séparément, éventuellement dans une structure observable. Et lorsque vous envoyez des actions Redux telles que le mouvement ou l'attaque des caractères du clavier, vous leur transmettez la graduation actuelle ou l'heure actuelle, afin que vous puissiez enregistrer à quelle graduation une action s'est produite et vos composants d'interface utilisateur peuvent enregistrer localement l'heure à laquelle une animation a commencé. .

Maintenant, vous avez séparé l'état d'animation global de l'état d'interaction, et si vous voulez faire une "relecture" en utilisant uniquement l'état Redux ou en rejouant des actions, vous pouvez, et vous n'êtes pas gêné par l'incrémentation des graduations dans le cadre de votre état arbre.

De manière générale, il est également préférable de coordonner les animations en passant les heures de démarrage / d'arrêt via Redux et en laissant les composants maintenir le reste de l'état localement.

Cela ne s'applique pas uniquement aux jeux. Toute séquence d'animations étendue peut passer par cela, par exemple si vous souhaitez faire une série d'animations de longue durée sur un site à l'aide de React.

@jsonnull cool, c'est un excellent exemple, merci de partager ça.

Je dirais simplement - dans Redux, vous ne pouvez tout simplement pas stocker vos objets d'état avec des liens les uns avec les autres. Par exemple, l'utilisateur peut avoir de nombreux messages et les articles peuvent avoir de nombreux commentaires et je souhaite stocker ces objets d'une manière:

var user = {id: 'user1', posts: [], comments: []}
var post = {id: 'post1', user: user, comments: []}
user.posts.push(post);
var comment = {id: 'comment1', post: post, user: user}
post.coments.push(comment)
user.comments.push(comment)
appState.user = user

Redux n'autorise pas l'utilisation de hiérarchies d'objets avec des références circulaires. Dans Mobx (ou Cellx), vous pouvez simplement avoir des références un-à-plusieurs et plusieurs-à-plusieurs avec des objets et cela simplifie la logique métier à plusieurs reprises.

@bgnorlov Je ne pense pas que quoi que ce soit dans le package redux interdise les références circulaires comme vous parlez - elles rendent juste plus difficile de cloner votre state dans votre réducteur. En outre, il peut être plus facile d'apporter des modifications à l'état avec des références circulaires si vous utilisez Immutable.js pour représenter votre état, bien que je ne sois pas sûr.

Vous pouvez également modéliser les relations dans votre état redux en utilisant des clés étrangères, même si je comprends que ce n'est pas aussi pratique que d'utiliser des références d'objet de type ORM:

var user = {id: 'user1', postIds: [], commentIds: []}
var post = {id: 'post1', userId: user.id, commentIds: []}
user.postIds.push(post.id);
var comment = {id: 'comment1', postId: post.id, userId: user.id}
post.commentIds.push(comment.id)
user.commentIds.push(comment.id)
appState.userId = user.id
appState.posts = {[post.id]: post}
appState.comments = {[comment.id]: comment}

// then join things like so:
var postsWithComments = _.map(appState.posts, post => ({
  ...post,
  comments: post.commentIds.map(id => appState.comments[id]),
})

@ jedwards1211 pour cloner l'état avec des références circulaires dans redux, le réducteur doit renvoyer chaque nouvelle copie d'un objet affecté par des modifications. Si la référence à un objet change, l'objet associé doit également être une nouvelle copie et cela sera répété de manière récursive et générera un nouvel état à chaque modification. Immutable.js ne peut pas stocker de références circulaires.
Avec l'approche de normalisation, lorsqu'un gestionnaire d'événements a besoin de certaines données, il doit à chaque fois prendre l'objet par son identifiant à partir de l'état global. Par exemple - je dois filtrer les frères et sœurs des tâches quelque part dans le gestionnaire d'événements (les tâches peuvent avoir une structure hiérarchique) Avec redux, je dois envoyer des thunk pour avoir accès à l'état de l'application

var prevTask = dispatch((_, getState)=>getState().tables.tasks[task.parentId]).children.map(childId=>dispatch((_, getState)=>getState().tables.tasks[childId])).filter(task=>...) [0]

ou avec des sélecteurs

var prevTask = dispatch(getTaskById(task.parentId)).children.map(childId=>dispatch(getTaskById(childId)).filter(task=>...)[0]

et ce passe-partout transforme le code en désordre par rapport à la version mobx quand je peux simplement écrire

var prevTask = task.parent.children.filter(task=>...)[0]

@ jedwards1211 , @bgnorlov : FWIW, c'est l'une des raisons pour lesquelles j'aime Redux-ORM . Cela vous permet de garder votre magasin Redux normalisé, mais simplifie ces mises à jour relationnelles et ces recherches. En fait, ce dernier exemple est fondamentalement identique à Redux-ORM.

Je viens d'écrire quelques articles de blog décrivant les bases de Redux-ORM , ainsi que les concepts de base et l'utilisation avancée .

@markerikson cool, merci pour le tuyau!

@gaearon

Une chose à noter est que nous n'avons pas l'intention que Redux soit utilisé pour tous les états. Tout ce qui semble important pour l'application. Je dirais que les entrées et l'état d'animation devraient être gérés par React (ou une autre abstraction d'état éphémère). Redux fonctionne mieux pour des choses comme les données extraites et les modèles modifiés localement.

J'ai trouvé ce commentaire incroyablement utile. Je sais que je suis en retard à la fête, mais c'est peut-être une partie de mon argument. Plus d'un an depuis la publication de ce commentaire et c'est toujours le seul endroit où j'ai vu cette idée s'exprimer.

Venant d'un fond angulaire et résolument sans flux / redux, il est très difficile de formuler cette idée par vous-même. Surtout quand de nombreux exemples créent encore des actions pour chaque keyup dans une zone de texte. Je souhaite que quelqu'un mette cette citation dans un texte de 50px en haut de chaque page de documentation Redux.

@leff D'accord. J'avais synthétisé cette idée exacte il y a quelque temps, mais elle n'est pas assez appelée. Même si vous utilisez Redux pour la gestion de l'historique, il y a souvent beaucoup d'états éphémères qui n'ont pas besoin d'alourdir votre gestion d'état de style Redux.

Ce ne sera pas nécessairement une surprise pour les personnes qui ont eu l'occasion de travailler sur, par exemple, une application de bureau mature qui effectue des annulations / rétablissements riches. Mais pour les océans de nouveaux arrivants venant à ces idées via Redux, ce sera une révélation.

@bgnorlov c'est un bon point qu'il est impossible de stocker des références circulaires avec Immutable.js, je n'y ai jamais pensé! Je pense que c'est une fonctionnalité intéressante.

De nos jours, nous avons tendance à stocker presque tout dans Redux, même s'il s'agit d'un état transitoire spécifique à la vue que nous n'utilisons jamais en dehors de son contexte (par exemple, un état pour une instance redux-form ). Dans la plupart des cas, nous pourrions déplacer un tel état hors du redux sans subir de problèmes. Mais l'avantage est qu'il est là au cas où nous aurions besoin de composants externes pour y répondre à l'avenir. Par exemple, nous pourrions avoir une icône dans la barre de navigation qui change lorsqu'un formulaire contient des erreurs ou est en cours de soumission.

Je dirais que la chose la plus importante à garder à l'esprit est que mettre tout l'état normalisé dans Redux (c'est-à-dire tout ce qui doit être joint pour rendre les vues) le rendra plus facile à utiliser. Un état qui n'a pas besoin d'être joint à quoi que ce soit peut probablement vivre en dehors de Redux sans vous causer de problèmes, mais cela ne fait généralement pas de mal de le mettre dans Redux non plus.

FWIW, les documents soulignent que tout ne doit pas aller dans Redux, par http://redux.js.org/docs/faq/OrganizingState.html#organizing -state-only-redux-state.

Il y a eu pas mal de discussions récentes en ligne sur ce que Redux est «approprié». Certaines personnes voient les avantages de mettre littéralement tout dans Redux, d'autres trouvent que c'est trop compliqué et ne veulent stocker que les données récupérées à partir d'un serveur. Donc, il n'y a certainement pas de règle fixe ici.

Comme toujours, si les gens ont des idées pour améliorer la documentation, les RP sont les bienvenus :)

Si jamais vous avez besoin de réutiliser des réducteurs et de pouvoir allouer des "sous-états" dans votre état redux, j'ai développé un plugin pour le faire sans effort (avec intégration React)

https://github.com/eloytoro/react-redux-uuid

@eloytoro IMO si vous rencontrez des problèmes avec un réducteur tiers ayant des types d'action codés en dur ou un emplacement dans l'état, vous devriez ouvrir un problème dans le projet ... d'ici peu, cela deviendra une pratique de conception inacceptable à mesure que les gens apprendront le réducteur / action réutilisable modèle de créateur.

@eloytoro J'allais écrire quelque chose comme ça. Merci beaucoup pour la référence!

@ jedwards1211 Je pense que vous avez une mauvaise idée. Je n'ai pas mentionné de réducteurs tiers ni de problèmes avec eux de quelque manière que ce soit, en essayant simplement de montrer comment mon extrait de code peut résoudre le problème de rendre les réducteurs réutilisables dans des collections avec des tailles dynamiques

@avesus espère que cela fonctionne pour vous

@eloytoro ah, cela a plus de sens.

L'année dernière, j'ai créé un jeu assez complexe en utilisant Redux. J'ai également participé à un autre projet qui n'utilise pas Redux, mais plutôt un cadre minimaliste personnalisé qui sépare tout en magasins d'interface utilisateur et magasins de serveurs (y compris les entrées). Sans entrer dans trop de détails, mes conclusions jusqu'à présent sont les suivantes:

Un seul État est toujours mieux, cependant, n'en faites pas trop. Obtenir chaque keyDown () dans le magasin et revenir à la vue est juste déroutant et inutile. Les états transitoires doivent donc être gérés par les états des composants locaux (tels que ceux de React).

Je pense que depuis que le redux et la réaction ont enflammé le web observable, la quantité de bonne animation sur les pages a diminué.

@ mib32 vous avez peut-être raison! Espérons que les gens finiront par s'habituer à créer une bonne animation dans React.

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