Redux: Un souci sur combineReducer, lorsqu'une action est liée à plusieurs réducteurs

Créé le 21 août 2015  ·  52Commentaires  ·  Source: reduxjs/redux

Je travaille sur une application de chat construite avec React.js et Flux. En lisant la documentation de Redux, la fonction combineReducer me semble étrange. Par exemple:

Il y a trois magasins appelés messageStore , unreadStore , threadStore . Et il y a une action appelée NEW_MESSAGE . Les trois magasins mettront à jour les nouveaux messages. Dans Redux, les magasins sont des réducteurs combinés avec combineReducer comme:

message = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
unread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
thread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state

combineReducer {message, unread, thread}

Il me semble que dans de tels cas, le fractionnement des réducteurs ne me facilite pas la vie. Cependant, un magasin géant construit avec des js immuables peut-être une meilleure solution pour cela. Comme:

initialState = Immutable.fromJS
  message: []
  unread: []
  thread: []

allStore = (store=initialState, action) ->
  switch action.type
    when NEW_MESSAGE
        initialState
        .updateIn ['message'], (messages) -> messages # updated
        .updateIn ['urnead'], (urneads) -> unreads # updated
        .updateIn ['thread'], (threads) -> threads # updated
question

Commentaire le plus utile

pour une telle action, je dois la maintenir dans plusieurs réducteurs (ou fichiers après la croissance de mon application). Dans notre base de code actuelle, nous avons déjà vu 5 magasins gérer une même action, c'est un peu difficile à maintenir.

Que ce soit bon ou mauvais dépend de votre application. D'après mon expérience, avoir de nombreux magasins (ou réducteurs Redux) gérant la même action est en fait très pratique car cela divise les responsabilités et permet également aux gens de travailler sur des branches de fonctionnalités sans entrer en collision avec le reste de l'équipe. D'après mon expérience, il est plus facile de maintenir des mutations non liées séparément qu'une mutation géante.

Mais vous avez raison, il y a des cas où cela ne fonctionne pas très bien. Je dirais qu'ils sont souvent les symptômes d'un modèle d'état sous-optimal. Par exemple,

dans certains cas, un réducteur peut dépendre des données d'un autre (comme déplacer un message de non lu vers un message, ou même promouvoir un message en tant que nouveau fil d'un message à l'autre)

est le symptôme d'un problème. Si vous devez beaucoup déplacer des éléments dans votre état, peut-être que la forme de l'état doit être plus normalisée. Au lieu de { readMessages, unreadMessages } vous pouvez avoir { messagesById, threadsById, messageIdsByThreadIds } . Message lu? Très bien, activez state.messagesById[id].isRead . Vous voulez lire les messages d'un fil de discussion? Prenez state.threadsById[threadId].messageIds , puis messageIds.map(id => state.messagesById[id]) .

Lorsque les données sont normalisées, vous n'avez pas vraiment besoin de copier les éléments d'un tableau vers un autre tableau. La plupart du temps, vous retourniez des indicateurs ou ajoutiez / supprimiez / associiez / dissociiez des identifiants.

Tous les 52 commentaires

Pouvez-vous expliquer pourquoi vous pensez que combineReducers n'est pas utile dans ce cas? En effet, pour un état de type base de données, vous voulez souvent un seul réducteur (puis d'autres réducteurs pour l'état de l'interface utilisateur, par exemple, l'élément sélectionné, etc.).

Voir également:

https://github.com/rackt/redux/tree/master/examples/real-world/reducers
https://github.com/rackt/redux/blob/master/examples/async/reducers

pour l'inspiration.

Le premier exemple que vous avez mentionné est similaire à mon cas, requestType est géré dans les deux réducteurs.
https://github.com/rackt/redux/blob/master/examples/real-world/reducers/paginate.js#L28

Le fractionnement des réducteurs facilite la maintenance lorsque deux réducteurs sont séparés des autres. Mais lorsqu'ils livrent les mêmes actions, cela conduit à:

  • pour une telle action, je dois la maintenir dans plusieurs réducteurs (ou fichiers après la croissance de mon application). Dans notre base de code actuelle, nous avons déjà vu 5 magasins gérer une même action, c'est un peu difficile à maintenir.
  • dans certains cas, un réducteur peut dépendre des données d'un autre (comme déplacer un message de unread à message , ou même promouvoir un message en tant que nouveau fil de message à thread ). Il se peut donc que je doive obtenir des données d'un autre réducteur maintenant.

Je ne vois pas de solution intéressante à ces deux problèmes. En fait, je ne suis pas tout à fait sûr de ces deux problèmes, ils sont comme des compromis.

pour une telle action, je dois la maintenir dans plusieurs réducteurs (ou fichiers après la croissance de mon application). Dans notre base de code actuelle, nous avons déjà vu 5 magasins gérer une même action, c'est un peu difficile à maintenir.

Que ce soit bon ou mauvais dépend de votre application. D'après mon expérience, avoir de nombreux magasins (ou réducteurs Redux) gérant la même action est en fait très pratique car cela divise les responsabilités et permet également aux gens de travailler sur des branches de fonctionnalités sans entrer en collision avec le reste de l'équipe. D'après mon expérience, il est plus facile de maintenir des mutations non liées séparément qu'une mutation géante.

Mais vous avez raison, il y a des cas où cela ne fonctionne pas très bien. Je dirais qu'ils sont souvent les symptômes d'un modèle d'état sous-optimal. Par exemple,

dans certains cas, un réducteur peut dépendre des données d'un autre (comme déplacer un message de non lu vers un message, ou même promouvoir un message en tant que nouveau fil d'un message à l'autre)

est le symptôme d'un problème. Si vous devez beaucoup déplacer des éléments dans votre état, peut-être que la forme de l'état doit être plus normalisée. Au lieu de { readMessages, unreadMessages } vous pouvez avoir { messagesById, threadsById, messageIdsByThreadIds } . Message lu? Très bien, activez state.messagesById[id].isRead . Vous voulez lire les messages d'un fil de discussion? Prenez state.threadsById[threadId].messageIds , puis messageIds.map(id => state.messagesById[id]) .

Lorsque les données sont normalisées, vous n'avez pas vraiment besoin de copier les éléments d'un tableau vers un autre tableau. La plupart du temps, vous retourniez des indicateurs ou ajoutiez / supprimiez / associiez / dissociiez des identifiants.

J'aimerais essayer cela.

Je viens d'écrire une page de démonstration pour essayer redux. Je pense que combineReducer m'a apporté une certaine complexité. Et tous les exemples du dépôt utilisent combineReducer , y a-t-il encore une chance que je puisse utiliser un seul objet de données immuable comme modèle, plutôt qu'un objet JavaScript qui contient mes données?

@jiyinyiyong Je ne sais vraiment pas de quelle complexité vous parlez, mais n'hésitez pas à ne pas utiliser combineReducers . C'est juste une aide. Découvrez un assistant similaire qui utilise Immutable à la place . Ou écrivez le vôtre bien sûr.

J'ai écrit mon propre assistant combineReducers() principalement pour prendre en charge Immutable.js, mais cela peut prendre des réducteurs supplémentaires traités comme des réducteurs de racine:
https://github.com/jokeyrhyme/wow-healer-bootcamp/blob/master/utils/combineReducers.js

Le premier argument correspond à la forme de votre état et transmet les sous-états de niveau supérieur aux sous-réducteurs correspondants que vous fournissez. C'est plus ou moins la même que la version officielle.

Cependant, il est facultatif d'ajouter zéro ou plusieurs arguments supplémentaires, ceux-ci sont traités comme les autres réducteurs de racine. Ces réducteurs sont passés à l'état complet. Cela permet aux actions d'être distribuées d'une manière qui nécessite plus d'accès que le modèle de sous-réducteur recommandé.

Ce n'est évidemment pas une bonne idée d'abuser de cela, mais j'ai trouvé le cas étrange où il est agréable d'avoir plusieurs sous-réducteurs ainsi que plusieurs réducteurs de racine.

Peut-être qu'au lieu d'ajouter plusieurs réducteurs racine, une étape intermédiaire pourrait être d'utiliser les arguments supplémentaires pour définir les sous-réducteurs qui nécessitent un accès plus large (encore moins que l'accès total). Par exemple:

function a (state, action) { /* only sees within a */ return state; }
function b (state, action) { /* only sees within b */ return state; }
function c (state, action) { /* only sees within c */ return state; }

function ab (state, action) { /* partial state containing a and b */ return state; }
function bc (state, action) { /* partial state containing b and c */ return state; }

let rootReducer = combineReducers(
  { a, b, c },
  [ 'a', 'b', ab ],
  [ 'b', 'c', bc ]
);

Vaut-il la peine de soumettre un PR pour les réducteurs partiels et les réducteurs de racines supplémentaires? Est-ce une idée horrible ou est-ce quelque chose qui pourrait être utile à suffisamment d'autres personnes?

J'ai plus envie d'apprendre comment l' halogène et l' orme traitent ces problèmes. JavaScript est un mélange de plusieurs diagrammes de programme et nous conduit à de nombreuses façons. Je ne sais plus quel est le bon du FRP et du flux de données unidirectionnel.

J'ai trouvé que c'était un point de confusion pour moi aussi. Je viens de poster mon cas d'utilisation / solution ici.

J'ai trois sous-éditeurs qui gèrent un éventail de trois types de ressources différents (clients, projets, emplois). Lorsque je supprime un client ( REMOVE_CLIENT ), tous les projets et travaux associés doivent être supprimés. Les travaux sont liés aux clients via un projet, le réducteur de travaux doit donc avoir accès aux projets pour effectuer la logique de jointure. Le réducteur de travaux doit obtenir une liste de tous les ID de projet appartenant au client en cours de suppression, puis supprimer tout travail correspondant du magasin.

J'ai contourné ce problème avec le middleware suivant:

export default store => next => action => {
  next({ ...action, getState: store.getState });
}

Pour que chaque action ait accès au magasin racine via action.getState() .

@ rhys-vdw merci pour ça! Un middleware vraiment utile :)

@ rhys-vdw Merci pour cela - semble être une bonne solution. Comment gérez-vous les cas extrêmes / l'intégrité référentielle, par exemple lorsqu'une entité est partagée par deux (ou plus) autres entités ou pointe vers un enregistrement inexistant lors de la suppression. Je souhaite juste entendre vos pensées à ce sujet.
@gaearon Existe-t-il un moyen documenté dans Redux de résoudre ce genre de problème d'intégrité référentielle des entités normalisées?

Il n'y a pas de moyen spécifique. J'imagine que l'on peut automatiser cela en définissant un schéma et en générant des réducteurs basés sur ce schéma. Ils «sauraient» alors comment ramasser les «déchets» lorsque des choses sont supprimées. Cependant, tout cela est très ondulé et nécessite un effort de mise en œuvre :-). Personnellement, je ne pense pas que la suppression soit assez fréquente pour justifier un véritable nettoyage. Je voudrais généralement retourner un champ status sur le modèle et m'assurer de ne pas afficher les éléments supprimés lors de leur sélection. Ou, bien sûr, vous pouvez actualiser lors de la suppression s'il ne s'agit pas d'une opération courante.

@gaearon Vive la réponse ultra rapide. Ouais… Je pense que l'on fait le même effort pour traiter l'intégrité référentielle lorsqu'on utilise des références avec des bases de données basées sur des documents comme Mongo. Je pense qu'il vaut la peine soit d'avoir un état pur , sans aucune entité à ordures flottant autour. Je ne sais pas si ces entrées fantômes pourraient jamais vous faire trébucher, surtout lorsque vous avez beaucoup d'opérations CRUD sur votre état.

Cependant, je pense aussi que ce serait un atout si Redux prenait en charge les entités normalisées et intégrées. Et on pourrait choisir une stratégie qui correspond à son objectif. Mais je suppose que pour les entités intégrées, il faut utiliser nested reducers qui est un anti-modèle selon la documentation Redux.

J'ai trouvé difficile d'accéder au reste de l'état du magasin dans un réducteur. Cela rend l'utilisation des moissonneurs-réducteurs imbriqués très difficiles dans certains cas. Ce serait bien s'il existe des méthodes pour accéder au reste de l'état comme .parent() ou .root() ou équivalent.

Accéder à l'état des autres réducteurs dans un réducteur est le plus souvent un anti-pattern.
Il peut généralement être résolu par:

  • Supprimer cette logique du réducteur et la déplacer vers un sélecteur;
  • Transmettre des informations supplémentaires à l'action;
  • Laisser le code de vue effectuer deux actions.

Merci! Je suis d'accord avec ça. Je viens de trouver que cela devenait plus complexe après avoir essayé de passer l'état racine en réducteurs :-)

Oui, le problème avec le passage de l'état racine est que les réducteurs sont couplés à la forme d'état de l'autre, ce qui complique toute refactorisation ou modification de la structure d'état.

D'accord, je vois clairement les avantages d'avoir un état normalisé et je traite la partie redux de mon genre d'état comme une DB.

Je suis toujours en train de penser si l' approche de (marquage des entités pour suppression) ou @ rhys-vdw est meilleure (résoudre les références et supprimer les références associées en place) .

Des idées / expériences du monde réel?

Si le redux ressemblait plus à l'orme et que l'état n'était pas normalisé, alors le découplage des réducteurs de la forme de l'état fait sens. Cependant, comme l'état est normalisé, il semble que les réducteurs ont souvent besoin d'accéder à d'autres parties de l'état de l'application.

Les réducteurs sont déjà couplés de manière transitoire à l'application car les réducteurs dépendent des actions. Vous ne pouvez pas réutiliser de manière triviale les réducteurs dans une autre application redux.

Travailler sur le problème en poussant l'accès à l'état dans des actions et en utilisant un middleware semble être une forme de couplage beaucoup plus laide que de permettre aux réducteurs d'accéder directement à l'état racine. Désormais, les actions qui, souvent, ne l'exigent pas, sont également associées à la forme de l'état et il y a plus d'actions que de réducteurs et plus d'endroits où changer.

Passer l'état en vue en actions isole peut-être mieux les changements de forme d'état, mais si un état supplémentaire est nécessaire, car des fonctionnalités sont ajoutées, tout comme de nombreuses actions doivent être mises à jour.

IMHO permettant aux réducteurs d'accéder à l'état racine entraînerait moins de couplage global que les solutions de contournement actuelles.

@kurtharriger : notez que bien que le modèle commun recommandé soit de structurer l'état de Redux par domaine et d'utiliser combineReducers pour gérer chaque domaine séparément, c'est _juste_ une recommandation. Il est tout à fait possible d'écrire votre propre réducteur de niveau supérieur qui gère les choses différemment, y compris en passant l'état entier à des fonctions de sous-réducteur spécifiques. En fin de compte, vous n'avez vraiment qu'une seule fonction de réduction , et la façon dont vous la décomposez en interne dépend entièrement de vous. combineReducers est juste un utilitaire utile pour un cas d'utilisation courant.

La réponse de la FAQ Redux sur le fractionnement de la logique métier contient également de bonnes discussions sur ce sujet.

Oui, une autre option serait d'utiliser simplement un seul réducteur dans une fonction géante et / ou d'écrire votre propre fonction combineReducer plutôt que d'utiliser la fonction intégrée.
Avoir une méthode combinerReducers intégrée qui ajoute un argument d'état racine supplémentaire pourrait être utile, mais les PR qui ont tenté d'ajouter cette fonctionnalité ont été fermés en tant qu'anti-pattern. Je voulais juste faire valoir le point que les alternatives proposées à mon humble avis aboutissent à un couplage beaucoup plus global et peut-être que cela ne devrait pas être considéré comme un anti-modèle. De plus, si l'état racine a été ajouté en tant qu'argument supplémentaire, ce n'est pas comme si vous étiez obligé d'utiliser, le couplage reste donc un opt-in.

Nous voulons que ce couplage soit explicite. C'est explicite quand vous le faites manuellement et c'est littéralement N lignes de code où N est le nombre de réducteurs dont vous disposez. N'utilisez simplement pas combineReducers et écrivez ce réducteur parent à la main.

Je suis nouveau pour redux / réagir mais je demanderais humblement ceci: si les réducteurs composés accédant à l'état de l'autre est un anti-modèle parce qu'il ajoute un couplage entre des zones disparates de la forme de l'état, je dirais que ce couplage a déjà lieu dans mapStateToProps ( ) de connect (), puisque cette fonction fournit l'état racine et les composants tirent de partout / n'importe où pour obtenir leurs accessoires.

Si des sous-arborescences d'état doivent être encapsulées, les composants doivent avoir accès à l'état via des accesseurs qui masquent ces sous-arborescences, de sorte que la forme d'état interne à ces étendues puisse être gérée sans refactorisation.

Au fur et à mesure que j'apprends le redux, je lutte avec ce que j'imagine être un problème à long terme avec mon application, qui est un couplage étroit de la forme de l'état à travers l'application - pas par des réducteurs mais par mapStateToProps - comme une barre latérale de commentaire qui doit connaître le auth user name (chacun étant réduit par un réducteur de commentaire vs un réducteur d'auth, par exemple.) J'essaie d'encapsuler tous les accès d'état dans des méthodes d'accesseur qui font ce genre d'encapsulation, même dans mapStateToProps, mais j'ai l'impression que je pourrais aboyer le mauvais arbre.

@jwhiting : c'est l'un des nombreux objectifs de l'utilisation de "fonctions de sélection" pour extraire les données dont vous avez besoin de l'état, en particulier dans mapStateToProps . Vous pouvez masquer l'état de votre forme afin qu'il y ait un nombre minimal d'emplacements qui ont réellement besoin de savoir où se trouve state.some.nested.field . Si vous ne l'avez pas encore vu, consultez la bibliothèque de l'utilitaire Reselect et la section Computing Derived Data de la documentation Redux.

Ouais. Vous n'avez pas non plus besoin d'utiliser Reselect si votre volume de données est petit mais utilisez toujours des sélecteurs (écrits à la main). Consultez l'exemple shopping-cart pour cette approche.

@markerikson @gaearon cela a du sens et je vais explorer cela, merci. L'utilisation de sélecteurs dans mapStateToProps pour tout ce qui n'est pas la principale préoccupation de ce composant (ou peut-être pour tous les accès d'état) protégerait les composants des changements de forme d'états étrangers. Je voudrais cependant trouver un moyen de rendre l'encapsulation d'état plus strictement appliquée dans mon projet, afin que la discipline du code ne soit pas le seul gardien et accueillir les suggestions.

Conserver tout le code dans un seul réducteur n'est pas vraiment une bonne séparation de
préoccupations. Si votre réducteur est appelé par la racine, vous pouvez l'appeler manuellement
et passez explicitement l'état racine comme je pense que vous le suggérez, mais si votre
la hiérarchie du réducteur parent contient des combinerReducers que vous devez aller déchirer
en dehors. Alors pourquoi ne pas le rendre plus compossible afin que vous n'ayez pas besoin de le déchirer
plus tard ou recourir aux thunks comme la plupart des gens semblent le faire?
Le sam, 16 avril 2016 à 20:27 Josh Whiting [email protected]
a écrit:

@markerikson https://github.com/markerikson @gaearon
https://github.com/gaearon qui a du sens et je vais explorer cela,
Je vous remercie. Utilisation de sélecteurs dans mapStateToProps pour tout ce qui n'est pas
La principale préoccupation du composant (ou peut-être pour tous les accès de l'État) protégerait
composants provenant de modifications de la forme d'un état étranger. Je voudrais trouver un moyen
pour rendre l'encapsulation d'état plus strictement appliquée dans mon projet, cependant,
afin que la discipline du code ne soit pas le seul gardien et bienvenue
suggestions là-bas.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/reactjs/redux/issues/601#issuecomment -210940305

@kurtharriger : nous sommes tous d'accord pour dire que garder le moindre combineReducers comme utilitaire intégré pour un cas d'utilisation courant. Si vous ne souhaitez pas utiliser cet utilitaire tel qu'il existe actuellement, vous n'avez pas - vous êtes tout à fait la bienvenue à structurer vos réducteurs votre manière, que ce soit en train d' écrire tout à la main, ou en écrivant votre propre variation de combineReducers qui fait les choses d'une manière plus adaptée à vos propres besoins.

Puisque nous parlons de modèles et de séparation des préoccupations, je rencontre en fait quelque chose de lié, mais avec des actions à la place.

À savoir, le modèle ajax shouldFetch que je vois à divers endroits semble être un couplage étroit avec la forme de l'état. Par exemple, ici . Dans cet exemple, que se passe-t-il si je réutilise le réducteur mais que je n'ai pas l'objet Entités à la racine de l'état? Cette action échouerait alors. Quelle est la recommandation ici? Dois-je ajouter des actions spécifiques à un cas? Ou une action qui accepte un sélecteur?

@shadowii Faire importer un sélecteur par un créateur d'action me semble bien. Je colocaliserais les sélecteurs avec des réducteurs afin que la connaissance de la forme de l'état soit localisée dans ces fichiers.

Quel est le problème avec le fait que différents réducteurs gèrent le même type d'action?
Par exemple, si j'ai un réducteur account et auth , ils gèrent tous les deux le type d'action LOGIN_SUCCESS (avec le jeton d'authentification et les données de compte dans la charge utile), chacun on ne met à jour que la tranche d'état qu'ils gèrent. Je me suis retrouvé à utiliser cette approche à plusieurs reprises lorsqu'une action affecte différentes parties de l'État.

Quel est le problème avec le fait que différents réducteurs gèrent le même type d'action?

Il n'y a pas de problème, c'est un modèle très courant et utile. En fait, ce sont les actions _reason_ qui existent: si des actions sont mappées sur des réducteurs 1: 1, il n'y aurait guère d'intérêt à avoir des actions.

@ rhys-vdw j'ai essayé d'utiliser le middleware que vous avez publié.

customMiddleare.js

const customMiddleware =  store => next => action => {
  next({ ...action, getState: store.getState });
};

et dans ma création de magasin j'ai

import customMiddleware from './customMiddleware';

var store = createStore(
        rootReducer,
        initialState,
        applyMiddleware(
            reduxPromise,
            thunkMiddleware,
            loggerMiddleware,
            customMiddleware
        )
    );
return store;

Je reçois l'erreur suivante:

applyMiddleware.js? ee15: 49 Uncaught TypeError: le middleware n'est pas une fonction (…)
(fonction anonyme) @ applyMiddleware.js? ee15: 49
(fonction anonyme) @ applyMiddleware.js? ee15: 48
createStore @ createStore.js? fe4c: 65
configureStore @ configureStore.js? ffde: 45
(fonction anonyme) @ index.jsx? fdd7: 25
(fonction anonyme) @ bundle.js: 1639
webpack_require @ bundle.js: 556
fn @ bundle.js: 87 (fonction anonyme) @ bundle.js: 588
webpack_require @ bundle.js: 556
(fonction anonyme) @ bundle.js: 579
(fonction anonyme) @ bundle.js: 582

@ mars76 : Vous customMiddleware.js , alors vous n'exportez rien. En général, vous passez quatre middlewares à applyMiddleware , et probablement l'un d'entre eux n'est pas une référence valide. Reculez, supprimez-les tous, ajoutez-en de nouveau un à la fois, voyez lequel n'est pas valide et découvrez pourquoi. Vérifiez les instructions d'importation et d'exportation, revérifiez comment vous êtes censé initialiser les choses, etc.

Salut,

Merci @markerikson

J'ai converti la syntaxe comme ci-dessous et ça va maintenant:

customMiddleware.js

export function customMiddleware(store) { return function (next) { return function (action) { next(Object.assign({}, action, { getState: store.getState })); }; }; } console.log(customMiddleware);

Quelle que soit la façon dont j'essaie d'accéder au magasin dans le créateur d'action lui-même (pas dans le réducteur). J'ai besoin de passer un appel Async et de passer différentes parties de l'état du magasin gérées par différents réducteurs.

C'est ce que je fais. Je ne sais pas si c'est la bonne approche.

J'expédie une action avec les données et à l'intérieur du réducteur, j'ai maintenant accès à l'ensemble de l'État et j'extrais les parties nécessaires dont j'ai besoin et je crée un objet et envoie une autre action avec ces données qui seront disponibles dans mon Créateur d'action où je je fais un appel Asyn en utilisant ces données.

Ma plus grande préoccupation est que les réducteurs appellent maintenant des méthodes de création d'action.

Appréciez tous les commentaires.

Merci

@ mars76 : encore une fois, vous ne devriez _pas jamais _ essayer de distribuer depuis un réducteur.

Pour accéder au magasin à l'intérieur de vos créateurs d'action, vous pouvez utiliser le middleware redux-thunk . Vous voudrez peut-être également lire la question FAQ sur le partage de données entre les réducteurs , ainsi que l'essentiel que j'ai écrit avec quelques exemples d'utilisations courantes des thunk .

Merci beaucoup @markerikson.

Mon mal, je ne savais pas à propos de getState () dans redux-thunk. Cela rend mon travail beaucoup plus facile et laisse mes réducteurs purs.

@gaearon lorsque vous avez mentionné qu'un créateur d'action importait un sélecteur , pensiez -vous que l'état entier serait transmis au créateur d'action pour qu'il soit transféré au sélecteur, ou est-ce que je manque quelque chose?

@tacomanator : Pour

La plupart du temps, si vous utilisez un sélecteur dans un créateur d'action, vous le faites à l'intérieur d'un thunk. Les Thunks ont accès à getState , donc l'arborescence d'état entière est accessible:

import {someSelector} from "./selectors";

function someThunk(someParameter) {
    return (dispatch, getState) => {
        const specificData = someSelector(getState(), someParameter);

        dispatch({type : "SOME_ACTION", payload : specificData});    
    }
}

@markerikson merci, c'est logique. Pour info, la raison pour laquelle je pose la question est principalement d'utiliser les données dérivées par les sélecteurs pour la logique métier de validation au lieu de les envoyer à un réducteur, mais cela me donne également matière à réflexion.

Un hack rapide que j'ai utilisé pour combiner des réducteurs en un seul était

const reducers = [ reducerA, reducerB, ..., reducerZ ];

let reducer = ( state = initialState, action ) => {
    return reducers.reduce( ( state, reducerFn ) => (
        reducerFn( state, action )
    ), state );
};

reducerA à reducerZ sont les fonctions de

@markerikson Ah bien, la première fois en utilisant react / redux / etc et

Heh, bien sûr.

Pour info, vous voudrez peut-être lire la FAQ: "Dois-je utiliser combineReducers ?" et Structurer les sections série de didacticiels "Practical Redux" contient quelques articles qui illustrent certaines techniques de réduction avancées (voir les parties 7 et 8).

Mes deux cents en utilisant redux-logic est d'augmenter l'action de l'état racine dans la fonction transform .

Exemple:

const formInitLogic = createLogic({
  type: 'FORM_INIT',
  transform({ getState, action }, next) {
    const state = getState();
    next({
      ...action,
      payload: {
        ...action.payload,
        property1: state.branch1.someProperty > 5,
        property2: state.branch1.anotherProperty + state.branch2.yetAnotherProperty,
      },
    });
  },
});

je pense que c'est plus une question de structure de code:
"n'accédez pas directement aux actions des sous-états"
après avoir utilisé redux dans de nombreux projets, j'ai trouvé que la meilleure structure de code consiste à traiter chaque sous-état comme un composant complètement séparé, chacun de ces composants a son propre dossier et sa propre logique.Pour mettre en œuvre cette idée, j'utilise la structure de fichiers suivante:

  • racine redux

    • reducers.js export combine des réducteurs de tous les états

    • middleware.js export applyMiddleware pour tous les middleware

    • configureStore.js createStore en utilisant (reducers.js, middleware.js)

    • actions.js exportent des actions qui distribuent des actions à partir de dossiers d'états <== CECI

    • état1

    • initial.js ce fichier contient l'état initial (j'utilise immuable pour créer un enregistrement)

    • action-types.js ce fichier contient les types d'action (j'utilise dacho comme miroir de clé)

    • actions.js ce fichier contient les actions d'état

    • reducer.js ce fichier contient le réducteur d'état

    • état2

    • initial.js

    • action-types.js

      ....

Pourquoi ?
1- Il existe 2 types d'actions dans chaque application:

  • actions d'état (redux / state1 / actions.js) ces actions ont la responsabilité de ne changer que l'état, vous ne devez pas déclencher ces actions directement, toujours utiliser les actions d'application

    • actions d'application (redux / actions.js) ces actions ont la responsabilité de faire une logique d'application et peuvent déclencher des actions d'état

2- De plus, en utilisant cette structure, vous pouvez créer un nouvel état en copiant simplement un dossier et en le renommant;)

Qu'en est-il lorsque 2 réducteurs doivent changer la même propriété?

Disons que j'ai un réducteur que j'aimerais diviser en différents fichiers et qu'il existe une propriété appelée «erreur», que je change lorsque les requêtes http échouent pour une raison ou une autre.

Maintenant, à un moment donné, le réducteur est devenu trop gros, j'ai donc décidé de le diviser en plusieurs réducteurs, mais tous doivent changer cette propriété «erreur».

Alors quoi?

@ Wassap124 Il n'est pas possible pour deux réducteurs de gérer le même état. Voulez-vous dire que deux actions doivent changer la même propriété?

Sans plus de détails, vous préférerez peut-être utiliser un thunk pour envoyer une action "set error".

@ rhys-vdw Je suis juste un peu coincé à essayer de séparer un réducteur assez long.
Et en ce qui concerne votre suggestion, cela ne contredit-il pas la règle de l'absence d'effets secondaires?

@ Wassap124 , @ rhys-vdw: pour clarifier, il n'est pas possible pour deux réducteurs de gérer le même état _si vous n'utilisez que combineReducers _. Cependant, vous pouvez certainement écrire une autre logique de réduction pour s'adapter à votre propre cas d'utilisation - voir https://redux.js.org/recipes/structuringreducers/beyondcombinereducers .

Si vous faites de la spéléologie source rapide ...

Le combineReducers renvoie une signature de fonction comme ceci

return function combination(state = {}, action) {

voir https://github.com/reduxjs/redux/blob/master/src/combineReducers.js#L145

après avoir créé une fermeture pour référencer l'objet réducteur réel.

Par conséquent, vous pouvez ajouter quelque chose de simple comme ceci pour gérer globalement les actions dans le cas où vous devez hydrater et recharger l'état à partir du stockage.

const globalReducerHandler = () => {
  return (state = {}, action) => {
    if (action.type === 'DO_GLOBAL_THING') {
      return globallyModifyState(state)
    }
    return reducers(state, action)
  }
}

Cela peut ou non être un anti-modèle, mais le pragmatisme a toujours été plus important.

Au lieu d'appeler un réducteur depuis un autre réducteur (ce qui est un antipatron, car globalReducerHandler n'a rien à voir avec le réducteur qu'il appelle), utilisez reduceReducers , qui est essentiellement compose pour les réducteurs (enfin, c'est littéralement composition dans une monade (Action ->) ).

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