Je réécris mon application pour utiliser Flux et j'ai un problème avec la récupération de données dans les magasins. J'ai beaucoup de composants et ils s'emboîtent beaucoup. Certains d'entre eux sont grands ( Article
), certains sont petits et simples ( UserAvatar
, UserLink
).
J'ai eu du mal à savoir où dans la hiérarchie des composants je devais lire les données des magasins.
J'ai essayé deux approches extrêmes, dont aucune ne m'a beaucoup plu:
Tous les composants d'entité lisent leurs propres données
Chaque composant qui a besoin de certaines données du Store reçoit uniquement l'ID d'entité et récupère l'entité par lui-même.
Par exemple, Article
est passé articleId
, UserAvatar
et UserLink
sont passés userId
.
Cette approche présente plusieurs inconvénients importants (décrits dans l'exemple de code).
var Article = React.createClass({
mixins: [createStoreMixin(ArticleStore)],
propTypes: {
articleId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
article: ArticleStore.get(this.props.articleId);
}
},
render() {
var article = this.state.article,
userId = article.userId;
return (
<div>
<UserLink userId={userId}>
<UserAvatar userId={userId} />
</UserLink>
<h1>{article.title}</h1>
<p>{article.text}</p>
<p>Read more by <UserLink userId={userId} />.</p>
</div>
)
}
});
var UserAvatar = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
userId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
user: UserStore.get(this.props.userId);
}
},
render() {
var user = this.state.user;
return (
<img src={user.thumbnailUrl} />
)
}
});
var UserLink = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
userId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
user: UserStore.get(this.props.userId);
}
},
render() {
var user = this.state.user;
return (
<Link to='user' params={{ userId: this.props.userId }}>
{this.props.children || user.name}
</Link>
)
}
});
Inconvénients de cette approche:
- Il est frustrant d'avoir des centaines de composants potentiellement abonnés à des magasins;
- Il est difficile de savoir comment les données sont mises à jour et dans quel ordre, car chaque composant récupère ses données indépendamment;
- Même si vous avez peut-être déjà une entité dans l'état, vous êtes obligé de transmettre son ID aux enfants, qui la récupéreront à nouveau (ou bien rompront la cohérence).
Toutes les données sont lues une fois au niveau supérieur et transmises aux composants
Quand j'étais fatigué de traquer les bogues, j'ai essayé de mettre toutes les données récupérées au plus haut niveau. Cela s'est toutefois avéré impossible car pour certaines entités, j'ai plusieurs niveaux d'imbrication.
Par exemple:
- A
Category
contientUserAvatar
des personnes qui contribuent à cette catégorie; - Un
Article
peut avoir plusieursCategory
s.
Par conséquent, si je voulais récupérer toutes les données des magasins au niveau d'un Article
, j'aurais besoin de:
- Récupérer l'article de
ArticleStore
; - Récupérez toutes les catégories d'articles à partir de
CategoryStore
; - Récupérez séparément les contributeurs de chaque catégorie
UserStore
; - Transmettez en quelque sorte toutes ces données aux composants.
Encore plus frustrant, chaque fois que j'ai besoin d'une entité profondément imbriquée, je devrais ajouter du code à chaque niveau d'imbrication pour le transmettre en plus.
Résumer
Les deux approches semblent imparfaites. Comment résoudre ce problème de la manière la plus élégante?
Mes objectifs:
Les magasins ne devraient pas avoir un nombre insensé d'abonnés. C'est stupide pour chacun
UserLink
d'écouterUserStore
si les composants parents le font déjà.Si le composant parent a récupéré un objet du magasin (par exemple
user
), je ne veux pas qu'un composant imbriqué doive le récupérer à nouveau. Je devrais pouvoir le transmettre via des accessoires.Je ne devrais pas avoir à récupérer toutes les entités (y compris les relations) au niveau supérieur car cela compliquerait l'ajout ou la suppression de relations. Je ne veux pas introduire de nouveaux accessoires à tous les niveaux d'imbrication chaque fois qu'une entité imbriquée obtient une nouvelle relation (par exemple, la catégorie obtient un
curator
).
la source
Réponses:
La plupart des gens commencent par écouter les magasins pertinents dans un composant de vue contrôleur près du haut de la hiérarchie.
Plus tard, quand il semble que de nombreux accessoires non pertinents sont transmis à travers la hiérarchie à un composant profondément imbriqué, certaines personnes décideront que c'est une bonne idée de laisser un composant plus profond écouter les changements dans les magasins. Cela offre une meilleure encapsulation du domaine du problème sur lequel porte cette branche plus profonde de l'arborescence des composants. Il y a de bons arguments à faire valoir pour faire cela judicieusement.
Cependant, je préfère toujours écouter en haut et simplement transmettre toutes les données. Je prendrai parfois même l'état entier du magasin et le transmettrai à travers la hiérarchie en tant qu'objet unique, et je le ferai pour plusieurs magasins. J'aurais donc un accessoire pour l'
ArticleStore
état 's, et un autre pour l'UserStore
état' s, etc. Je trouve qu'éviter les vues de contrôleur profondément imbriquées maintient un point d'entrée singulier pour les données, et unifie le flux de données. Sinon, j'ai plusieurs sources de données, et cela peut devenir difficile à déboguer.La vérification de type est plus difficile avec cette stratégie, mais vous pouvez configurer une "forme", ou un modèle de type, pour le grand objet en tant que prop avec les PropTypes de React. Voir: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -validation
Notez que vous souhaiterez peut-être placer la logique d'association de données entre les magasins dans les magasins eux-mêmes. Donc, vous
ArticleStore
pouvezwaitFor()
le faireUserStore
et inclure les utilisateurs concernés avec chaqueArticle
enregistrement qu'il fournitgetArticles()
. Faire cela dans vos vues ressemble à pousser la logique dans la couche de vue, ce que vous devez éviter autant que possible.Vous pourriez également être tenté d'utiliser
transferPropsTo()
, et beaucoup de gens aiment faire cela, mais je préfère tout garder explicite pour la lisibilité et donc la maintenabilité.FWIW, je crois comprendre que David Nolen adopte une approche similaire avec son framework Om (qui est quelque peu compatible avec Flux ) avec un seul point d'entrée de données sur le nœud racine - l'équivalent dans Flux serait de n'avoir qu'une seule vue de contrôleur écoute de tous les magasins. Ceci est rendu efficace en utilisant
shouldComponentUpdate()
des structures de données immuables qui peuvent être comparées par référence, avec ===. Pour les structures de données immuables, consultez le mori de David ou immutable-js de Facebook . Ma connaissance limitée d'Om vient principalement de The Future of JavaScript MVC Frameworksla source
L'approche à laquelle je suis arrivé consiste à faire en sorte que chaque composant reçoive ses données (et non ses identifiants) comme accessoire. Si un composant imbriqué a besoin d'une entité associée, c'est au composant parent de le récupérer.
Dans notre exemple,
Article
devrait avoir unarticle
accessoire qui est un objet (probablement récupéré parArticleList
ouArticlePage
).Parce que
Article
veut aussi rendreUserLink
etUserAvatar
pour l'auteur de l'article, il s'abonneraUserStore
et resteraauthor: UserStore.get(article.authorId)
dans son état. Il sera ensuite renduUserLink
etUserAvatar
avec celathis.state.author
. S'ils souhaitent le transmettre davantage, ils le peuvent. Aucun composant enfant n'aura besoin de récupérer à nouveau cet utilisateur.Recommencer:
Cela résout mon problème assez bien. Exemple de code réécrit pour utiliser cette approche:
Cela rend les composants les plus intimes stupides mais ne nous force pas à compliquer l'enfer des composants de haut niveau.
la source
Ma solution est bien plus simple. Chaque composant qui a son propre état est autorisé à parler et à écouter les magasins. Ce sont des composants de type contrôleur. Les composants imbriqués plus profonds qui ne conservent pas l'état mais qui rendent uniquement des éléments ne sont pas autorisés. Ils ne reçoivent que des accessoires pour un rendu pur, très semblable à une vue.
De cette façon, tout passe des composants avec état aux composants sans état. Garder le nombre d'états bas.
Dans votre cas, Article serait avec état et par conséquent parle aux magasins et UserLink etc. ne serait rendu que pour qu'il reçoive article.user comme accessoire.
la source
Les problèmes décrits dans vos 2 philosophies sont communs à toute application monopage.
Ils sont abordés brièvement dans cette vidéo: https://www.youtube.com/watch?v=IrgHurBjQbg et Relay ( https://facebook.github.io/relay ) a été développé par Facebook pour surmonter le compromis que vous décrivez.
L'approche de Relay est très centrée sur les données. C'est une réponse à la question "Comment puis-je obtenir uniquement les données nécessaires pour chaque composant de cette vue en une seule requête vers le serveur?" Et en même temps, Relay s'assure que vous avez peu de couplage dans le code lorsqu'un composant est utilisé dans plusieurs vues.
Si Relay n'est pas une option , «Tous les composants d'entité lisent leurs propres données» me semble une meilleure approche pour la situation que vous décrivez. Je pense que l'idée fausse dans Flux est ce qu'est un magasin. Le concept de magasin n'existe pas pour être le lieu où un modèle ou une collection d'objets sont conservés. Les magasins sont des emplacements temporaires où votre application place les données avant le rendu de la vue. La vraie raison pour laquelle ils existent est de résoudre le problème des dépendances entre les données qui vont dans différents magasins.
Ce que Flux ne précise pas, c'est comment un magasin se rapporte au concept de modèles et de collection d'objets (à la Backbone). En ce sens, certaines personnes font en fait un magasin de flux un endroit où placer une collection d'objets d'un type spécifique qui n'est pas affleurant pendant tout le temps où l'utilisateur garde le navigateur ouvert mais, si je comprends bien, ce n'est pas ce qu'un magasin est censé être.
La solution est d'avoir une autre couche dans laquelle les entités nécessaires au rendu de votre vue (et potentiellement plus) sont stockées et mises à jour. Si vous utilisez cette couche qui abstrait des modèles et des collections, ce n'est pas un problème si vous les sous-composants doivent à nouveau interroger pour obtenir leurs propres données.
la source