web-dev-qa-db-fra.com

Comment charger dynamiquement des réducteurs pour la division de code dans une application Redux?

Je vais migrer vers Redux.

Mon application étant composée de nombreuses parties (pages, composants), je souhaite créer de nombreux réducteurs. Les exemples de Redux montrent que je devrais utiliser combineReducers() pour générer un réducteur.

Aussi, si j'ai bien compris, l'application Redux devrait avoir un magasin et il est créé une fois que l'application démarre. Lorsque le magasin est créé, je devrais passer mon réducteur combiné. Cela a du sens si l'application n'est pas trop grosse.

Mais que se passe-t-il si je construis plus d'un paquet JavaScript? Par exemple, chaque page d'application possède son propre ensemble. Je pense que dans ce cas, le réducteur combiné n'est pas bon. J'ai regardé à travers les sources de Redux et j'ai trouvé la fonction replaceReducer(). Cela semble être ce que je veux.

Je pouvais créer un réducteur combiné pour chaque partie de mon application et utiliser replaceReducer() lorsque je passais d'une partie à une autre de l'application.

Est-ce une bonne approche?

159
Pavel Teterin

Mise à jour: voir aussi comment Twitter le fait .

Ce n'est pas une réponse complète, mais devrait vous aider à démarrer. Notez que je ne jette pas les vieux réducteurs - je ne fais qu'en ajouter de nouveaux à la liste de combinaisons. Je ne vois aucune raison de jeter les anciens réducteurs. Même dans la plus grande application, il est peu probable que vous disposiez de milliers de modules dynamiques, ce qui vous permet de {pourrait} déconnecter certains réducteurs de votre application.

réducteurs.js

import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';

export default function createReducer(asyncReducers) {
  return combineReducers({
    users,
    posts,
    ...asyncReducers
  });
}

store.js

import { createStore } from 'redux';
import createReducer from './reducers';

export default function configureStore(initialState) {
  const store = createStore(createReducer(), initialState);
  store.asyncReducers = {};
  return store;
}

export function injectAsyncReducer(store, name, asyncReducer) {
  store.asyncReducers[name] = asyncReducer;
  store.replaceReducer(createReducer(store.asyncReducers));
}

routes.js

import { injectAsyncReducer } from './store';

// Assuming React Router here but the principle is the same
// regardless of the library: make sure store is available
// when you want to require.ensure() your reducer so you can call
// injectAsyncReducer(store, name, reducer).

function createRoutes(store) {
  // ...

  const CommentsRoute = {
    // ...

    getComponents(location, callback) {
      require.ensure([
        './pages/Comments',
        './reducers/comments'
      ], function (require) {
        const Comments = require('./pages/Comments').default;
        const commentsReducer = require('./reducers/comments').default;

        injectAsyncReducer(store, 'comments', commentsReducer);
        callback(null, Comments);
      })
    }
  };

  // ...
}

Il y a peut-être une meilleure façon de l'exprimer - je ne fais que montrer l'idée.

215
Dan Abramov

Voici comment je l'ai implémenté dans une application actuelle (basé sur le code de Dan tiré d'un problème de GitHub!)

// Based on https://github.com/rackt/redux/issues/37#issue-85098222
class ReducerRegistry {
  constructor(initialReducers = {}) {
    this._reducers = {...initialReducers}
    this._emitChange = null
  }
  register(newReducers) {
    this._reducers = {...this._reducers, ...newReducers}
    if (this._emitChange != null) {
      this._emitChange(this.getReducers())
    }
  }
  getReducers() {
    return {...this._reducers}
  }
  setChangeListener(listener) {
    if (this._emitChange != null) {
      throw new Error('Can only set the listener for a ReducerRegistry once.')
    }
    this._emitChange = listener
  }
}

Créez une instance de registre lorsque vous démarrez votre application, en transmettant des réducteurs qui seront inclus dans le groupe d'entrées:

// coreReducers is a {name: function} Object
var coreReducers = require('./reducers/core')
var reducerRegistry = new ReducerRegistry(coreReducers)

Ensuite, lors de la configuration du magasin et des itinéraires, utilisez une fonction que vous pouvez attribuer au registre des réducteurs pour:

var routes = createRoutes(reducerRegistry)
var store = createStore(reducerRegistry)

Où ces fonctions ressemblent à quelque chose comme:

function createRoutes(reducerRegistry) {
  return <Route path="/" component={App}>
    <Route path="core" component={Core}/>
    <Route path="async" getComponent={(location, cb) => {
      require.ensure([], require => {
        reducerRegistry.register({async: require('./reducers/async')})
        cb(null, require('./screens/Async'))
      })
    }}/>
  </Route>
}

function createStore(reducerRegistry) {
  var rootReducer = createReducer(reducerRegistry.getReducers())
  var store = createStore(rootReducer)

  reducerRegistry.setChangeListener((reducers) => {
    store.replaceReducer(createReducer(reducers))
  })

  return store
}

Voici un exemple live de base créé avec cette configuration et sa source:

Il couvre également la configuration nécessaire pour permettre le rechargement à chaud de tous vos réducteurs.

22
Jonny Buchanan

Il existe maintenant un module qui ajoute des réducteurs d'injection dans le magasin Redux. Il s’appelle Redux Injector. https://github.com/randallknutson/redux-injector

Voici comment l'utiliser:

  1. Ne pas combiner les réducteurs. Placez-les plutôt dans un objet (imbriqué) de fonctions comme vous le feriez normalement, mais sans les combiner.

  2. Utilisez createInjectStore de redux-injector au lieu de createStore de redux.

  3. Injectez de nouveaux réducteurs avec injectReducer.

Voici un exemple:

import { createInjectStore, injectReducer } from 'redux-injector';

const reducersObject = {
   router: routerReducerFunction,
   data: {
     user: userReducerFunction,
     auth: {
       loggedIn: loggedInReducerFunction,
       loggedOut: loggedOutReducerFunction
     },
     info: infoReducerFunction
   }
 };

const initialState = {};

let store = createInjectStore(
  reducersObject,
  initialState
);

// Now you can inject reducers anywhere in the tree.
injectReducer('data.form', formReducerFunction);

Divulgation complète: Je suis le créateur du module.

6
Randall Knutson

À partir d'octobre 2017:

  • Reedux

    met en œuvre ce que Dan a suggéré et rien de plus, sans toucher votre magasin, votre projet ou vos habitudes

Il existe également d'autres bibliothèques mais elles peuvent comporter trop de dépendances, moins d'exemples, une utilisation compliquée, sont incompatibles avec certains middlewares ou vous obligent à réécrire votre gestion d'état. Copié de la page d'introduction de Reedux: 

4
Silviu-Marian

Nous avons publié une nouvelle bibliothèque qui aide à moduler une application Redux et permet d'ajouter/de supprimer dynamiquement des réducteurs et des middlewares.

S'il vous plaît jeter un oeil à https://github.com/Microsoft/redux-dynamic-modules

Les modules offrent les avantages suivants:

  • Les modules peuvent être facilement réutilisés dans l'application ou entre plusieurs applications similaires.

  • Les composants déclarent les modules dont ils ont besoin et redux-dynamic-modules s'assure que le module est chargé pour le composant.

  • Les modules peuvent être ajoutés/retirés du magasin dynamiquement, ex. quand un composant monte ou quand un utilisateur effectue une action

Caractéristiques

  • Regroupez les réducteurs, le middleware et l'état en un seul module réutilisable.
  • Ajoutez et supprimez des modules d'un magasin Redux à tout moment.
  • Utilisez le composant inclus pour ajouter automatiquement un module lorsqu'un composant est rendu
  • Les extensions fournissent une intégration avec les bibliothèques populaires, y compris redux-saga et redux-observable

Exemples de scénarios

  • Vous ne voulez pas charger le code pour tous vos réducteurs en amont. Définissez un module pour certains réducteurs et utilisez DynamicModuleLoader et une bibliothèque telle que react-loadable pour télécharger et ajouter votre module au moment de l'exécution.
  • Certains réducteurs/middlewares courants doivent être réutilisés dans différents domaines de votre application. Définissez un module et incluez-le facilement dans ces domaines.
  • Vous avez un mono-repo qui contient plusieurs applications partageant un état similaire. Créez un paquet contenant quelques modules et réutilisez-les dans vos applications
0
Navneet Gupta

Voici un autre exemple avec partage de code et redux stores, assez simple et élégant à mon avis. Je pense que cela peut être très utile pour ceux qui recherchent une solution efficace.

Ce store est un peu simplifié, il ne vous oblige pas à avoir un espace de nom (reducer.name) dans votre objet state. Bien sûr, il peut y avoir une collision avec les noms mais vous pouvez le contrôler en créant une convention vos réducteurs et ça devrait aller.

0
Maksym Oliinyk