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?
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.
import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';
export default function createReducer(asyncReducers) {
return combineReducers({
users,
posts,
...asyncReducers
});
}
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));
}
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.
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.
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:
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.
Utilisez createInjectStore de redux-injector au lieu de createStore de redux.
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.
À partir d'octobre 2017:
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:
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.
Caractéristiques
Exemples de scénarios
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.