Dans le chapitre sur la conception de la forme de l'état , la documentation suggère de conserver votre état dans un objet indexé par ID:
Conservez chaque entité d'un objet stockée avec un ID comme clé et utilisez des ID pour la référencer à partir d'autres entités ou listes.
Ils continuent à déclarer
Considérez l'état de l'application comme une base de données.
Je travaille sur la forme de l'état pour une liste de filtres, dont certains seront ouverts (ils sont affichés dans une fenêtre contextuelle), ou ont des options sélectionnées. Quand j'ai lu «Pensez à l'état de l'application comme une base de données», j'ai pensé à les considérer comme une réponse JSON car elle serait renvoyée par une API (elle-même soutenue par une base de données).
Alors je pensais à ça comme
[{
id: '1',
name: 'View',
open: false,
options: ['10', '11', '12', '13'],
selectedOption: ['10'],
parent: null,
},
{
id: '10',
name: 'Time & Fees',
open: false,
options: ['20', '21', '22', '23', '24'],
selectedOption: null,
parent: '1',
}]
Cependant, la documentation suggère un format plus semblable à
{
1: {
name: 'View',
open: false,
options: ['10', '11', '12', '13'],
selectedOption: ['10'],
parent: null,
},
10: {
name: 'Time & Fees',
open: false,
options: ['20', '21', '22', '23', '24'],
selectedOption: null,
parent: '1',
}
}
En théorie, cela ne devrait pas avoir d'importance tant que les données sont sérialisables (sous la rubrique «État») .
J'ai donc opté pour l'approche tableau d'objets avec bonheur, jusqu'à ce que j'écrive mon réducteur.
Avec l'approche objet par identifiant (et l'utilisation libérale de la syntaxe de diffusion), la OPEN_FILTER
partie du réducteur devient
switch (action.type) {
case OPEN_FILTER: {
return { ...state, { ...state[action.id], open: true } }
}
Alors qu'avec l'approche tableau d'objets, c'est la plus verbeuse (et dépendante de la fonction d'assistance)
switch (action.type) {
case OPEN_FILTER: {
// relies on getFilterById helper function
const filter = getFilterById(state, action.id);
const index = state.indexOf(filter);
return state
.slice(0, index)
.concat([{ ...filter, open: true }])
.concat(state.slice(index + 1));
}
...
Mes questions sont donc triples:
1) La simplicité du réducteur est-elle la motivation pour aller avec l'approche objet-clé-par-id? Y a-t-il d'autres avantages à cette forme d'État?
et
2) Il semble que l'approche par identifiant d'objet rend plus difficile le traitement des entrées / sorties JSON standard pour une API. (C'est pourquoi j'ai opté pour le tableau d'objets en premier lieu.) Donc, si vous optez pour cette approche, utilisez-vous simplement une fonction pour le transformer d'avant en arrière entre le format JSON et le format de forme d'état? Cela semble maladroit. (Bien que si vous préconisez cette approche, cela fait-il partie de votre raisonnement selon lequel c'est moins maladroit que le réducteur de tableau d'objets ci-dessus?)
et
3) Je sais que Dan Abramov a conçu le redux pour être théoriquement indépendant de la structure des données d'état (comme suggéré par "Par convention, l'état de niveau supérieur est un objet ou une autre collection de valeurs-clés comme une carte, mais techniquement, il peut être n'importe quel type , " je souligne). Mais compte tenu de ce qui précède, est-il simplement "recommandé" de le conserver comme un objet associé à son ID, ou y a-t-il d'autres problèmes imprévus que je vais rencontrer en utilisant un tableau d'objets qui le rendent tel que je devrais simplement abandonner cela planifier et essayer de rester avec un objet saisi par ID?
la source
sort_by
?const sorted = _.sortBy(collection, 'attribute');
Réponses:
Q1: La simplicité du réducteur est le résultat de ne pas avoir à chercher dans le tableau pour trouver la bonne entrée. L'avantage est de ne pas avoir à chercher dans le tableau. Les sélecteurs et autres accesseurs de données peuvent accéder et accèdent souvent à ces éléments par
id
. Le fait de devoir rechercher dans la baie pour chaque accès devient un problème de performances. Lorsque vos baies deviennent plus volumineuses, le problème de performances s'aggrave fortement. De plus, à mesure que votre application devient plus complexe, affichant et filtrant les données à plus d'endroits, le problème s'aggrave également. La combinaison peut être préjudiciable. En accédant aux éléments parid
, le temps d'accès passe deO(n)
àO(1)
, ce quin
fait une énorme différence pour les éléments volumineux (ici les éléments de tableau).Q2: Vous pouvez utiliser
normalizr
pour vous aider à convertir de l'API en magasin. Depuis normalizr V3.1.0, vous pouvez utiliser denormalize pour aller dans l'autre sens. Cela dit, les applications sont souvent plus de consommateurs que de producteurs de données et, à ce titre, la conversion en magasin est généralement effectuée plus fréquemment.Q3: Les problèmes que vous rencontrerez en utilisant une baie ne sont pas tant des problèmes de convention de stockage et / ou d'incompatibilités, mais davantage de problèmes de performances.
la source
C'est l'idée clé.
1) Avoir des objets avec des identifiants uniques vous permet de toujours utiliser cet identifiant lors du référencement de l'objet, vous devez donc passer le minimum de données entre les actions et les réducteurs. C'est plus efficace que d'utiliser array.find (...). Si vous utilisez l'approche par tableau, vous devez passer l'objet entier et cela peut devenir désordonné très bientôt, vous pourriez finir par recréer l'objet sur différents réducteurs, actions, ou même dans le conteneur (vous ne le voulez pas). Les vues pourront toujours obtenir l'objet complet même si leur réducteur associé ne contient que l'ID, car lors du mappage de l'état, vous obtiendrez la collection quelque part (la vue obtient l'état complet pour le mapper aux propriétés). En raison de tout ce que j'ai dit, les actions finissent par avoir le minimum de paramètres, et réduisent la quantité minimale d'informations, essayez-le,
2) La connexion à l'API ne doit pas affecter l'architecture de votre stockage et des réducteurs, c'est pourquoi vous avez des actions, pour garder la séparation des soucis. Mettez simplement votre logique de conversion dans et hors de l'API dans un module réutilisable, importez ce module dans les actions qui utilisent l'API, et cela devrait être tout.
3) J'ai utilisé des tableaux pour les structures avec des identifiants, et voici les conséquences imprévues que j'ai subies:
J'ai fini par changer ma structure de données et réécrire beaucoup de code. Vous avez été prévenu, ne vous mettez pas en difficulté.
Aussi:
4) La plupart des collections avec des identifiants sont destinées à utiliser l'identifiant comme référence à l'objet entier, vous devriez en profiter. Les appels API recevront l'ID , puis le reste des paramètres, ainsi que vos actions et vos réducteurs.
la source
La principale raison pour laquelle vous souhaitez conserver les entités dans les objets stockés avec des ID sous forme de clés (également appelées normalisées ), est qu'il est vraiment fastidieux de travailler avec des objets profondément imbriqués (ce que vous obtenez généralement des API REST dans une application plus complexe) - à la fois pour vos composants et vos réducteurs.
Il est un peu difficile d'illustrer les avantages d'un état normalisé avec votre exemple actuel (car vous n'avez pas de structure profondément imbriquée ). Mais disons que les options (dans votre exemple) avaient également un titre et ont été créées par les utilisateurs de votre système. Cela donnerait à la réponse quelque chose comme ceci à la place:
Supposons maintenant que vous vouliez créer un composant qui affiche une liste de tous les utilisateurs qui ont créé des options. Pour ce faire, vous devez d'abord demander tous les éléments, puis parcourir chacune de leurs options et enfin obtenir le created_by.username.
Une meilleure solution serait de normaliser la réponse en:
Avec cette structure, il est beaucoup plus facile et plus efficace de lister tous les utilisateurs qui ont créé des options (nous les avons isolés dans entity.optionCreators, il suffit donc de parcourir cette liste).
Il est également assez simple d'afficher par exemple les noms d'utilisateur de ceux qui ont créé des options pour l'élément de filtre avec l'ID 1:
Une réponse JSON peut être normalisée en utilisant par exemple normalizr .
C'est probablement une recommandation pour des applications plus complexes avec de nombreuses réponses d'API profondément imbriquées. Dans votre exemple particulier, cela n'a pas vraiment d'importance.
la source
map
renvoie undefined comme ici , si les ressources sont extraites séparément, ce qui rendfilter
trop compliqué. Y a-t-il une solution?