Come caricare dinamicamente i riduttori per la suddivisione del codice in un'applicazione Redux?


189

Sto per migrare a Redux.

La mia applicazione è composta da molte parti (pagine, componenti), quindi voglio creare molti riduttori. Gli esempi di Redux mostrano che dovrei usare combineReducers()per generare un riduttore.

Anche a quanto ho capito, l'applicazione Redux dovrebbe avere un negozio e viene creata all'avvio dell'applicazione. Quando viene creato il negozio, dovrei passare il mio riduttore combinato. Questo ha senso se l'applicazione non è troppo grande.

Ma cosa succede se creo più di un bundle JavaScript? Ad esempio, ogni pagina dell'applicazione ha il proprio bundle. Penso che in questo caso l'unico riduttore combinato non sia buono. Ho guardato attraverso le fonti di Redux e ho trovato la replaceReducer()funzione. Sembra essere quello che voglio.

Potrei creare un riduttore combinato per ogni parte della mia applicazione e usarlo replaceReducer()quando mi sposto tra le parti dell'applicazione.

è un buon approccio?

Risposte:


245

Aggiornamento: vedi anche come lo fa Twitter .

Questa non è una risposta completa, ma dovrebbe aiutarti a iniziare. Si noti che non sto gettando via i vecchi riduttori: sto solo aggiungendo nuovi alla lista delle combinazioni. Non vedo alcun motivo per buttare via i vecchi riduttori: anche nella più grande app è improbabile che tu abbia migliaia di moduli dinamici, che è il punto in cui potresti voler disconnettere alcuni riduttori nella tua applicazione.

reducers.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);
      })
    }
  };

  // ...
}

Potrebbe esserci un modo più semplice di esprimerlo: sto solo mostrando l'idea.


13
Mi piacerebbe vedere questo tipo di funzionalità aggiunta al progetto. La capacità di aggiungere riduttori in modo dinamico è un must quando si ha a che fare con la suddivisione del codice e applicazioni di grandi dimensioni. Ho interi sotto alberi ai quali alcuni utenti potrebbero non accedere e caricare tutti i riduttori è uno spreco. Anche con applicazioni redux-ignore le applicazioni di grandi dimensioni possono davvero impilare i riduttori.
JeffBaumgardt,

2
A volte, è uno spreco più grande "ottimizzare" qualcosa di irrilevante.
XML,

1
Spero che il commento di cui sopra abbia un senso ... mentre sono rimasto senza spazio. Ma fondamentalmente non vedo un modo semplice per avere i riduttori combinati quando in un singolo ramo sul nostro albero di stato quando vengono caricati dinamicamente da percorsi diversi /homepagee quindi più di quel ramo viene caricato quando l'utente passa al loro profile.Un esempio di come per fare questo, sarebbe fantastico. Altrimenti ho difficoltà a spianare il mio albero di stato o devo avere nomi di rami molto specifici user-permissionseuser-personal
BryceHayden,

1
E come dovrei agire se ho uno stato iniziale?
Stalso,

3
github.com/mxstbr/react-boilerplate boilerplate utilizza la stessa identica tecnica menzionata qui per caricare i riduttori.
Pouya Sanooei,

25

Ecco come l'ho implementato in un'app corrente (basata sul codice di Dan da un problema di 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
  }
}

Crea un'istanza di registro quando fai il bootstrap della tua app, passando in riduttori che saranno inclusi nel bundle di voci:

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

Quindi, durante la configurazione dell'archivio e dei percorsi, utilizzare una funzione che è possibile assegnare al registro del riduttore a:

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

Dove queste funzioni assomigliano a:

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
}

Ecco un esempio live di base che è stato creato con questa configurazione e la sua fonte:

Copre anche la configurazione necessaria per consentire la ricarica a caldo di tutti i riduttori.


Grazie @jonny, solo un avvertimento, l'esempio sta generando un errore ora.
Jason J. Nathan,

manca la dichiarazione createReducer () nella tua risposta (so che è nella risposta di Dan Abrahamov ma penso che includerlo eviterebbe confusione)
Packet Tracer

6

Ora c'è un modulo che aggiunge i riduttori di iniezione nel negozio di redux. Si chiama Redux Injector .

Ecco come usarlo:

  1. Non combinare i riduttori. Invece, inseriscili in un oggetto (nidificato) di funzioni come faresti normalmente ma senza combinarli.

  2. Usa createInjectStore da redux-injector invece di createStore da redux.

  3. Iniettare nuovi riduttori con injectReducer.

Ecco un esempio:

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);

Divulgazione completa: sono il creatore del modulo.


4

A partire da ottobre 2017:

  • Reedux

    implementa ciò che Dan ha suggerito e niente di più, senza toccare il tuo negozio, il tuo progetto o le tue abitudini

Ci sono anche altre librerie ma potrebbero avere troppe dipendenze, meno esempi, un uso complicato, sono incompatibili con alcuni programmi o richiedono la riscrittura della gestione dello stato. Copiato dalla pagina introduttiva di Reedux:


2

Abbiamo rilasciato una nuova libreria che aiuta a modulare un'app Redux e consente di aggiungere / rimuovere in modo dinamico riduttori e middleware.

Dai un'occhiata a https://github.com/Microsoft/redux-dynamic-modules

I moduli offrono i seguenti vantaggi:

  • I moduli possono essere facilmente riutilizzati nell'applicazione o tra più applicazioni simili.

  • I componenti dichiarano i moduli necessari e i moduli dinamici-redux assicurano che il modulo sia caricato per il componente.

  • I moduli possono essere aggiunti / rimossi dal negozio in modo dinamico, ad es. quando viene montato un componente o quando un utente esegue un'azione

Caratteristiche

  • Raggruppare riduttori, middleware e stato in un unico modulo riutilizzabile.
  • Aggiungi e rimuovi moduli da un negozio Redux in qualsiasi momento.
  • Utilizzare il componente incluso per aggiungere automaticamente un modulo quando viene renderizzato un componente
  • Le estensioni forniscono l'integrazione con librerie popolari, tra cui redux-saga e redux-osservabili

Scenari di esempio

  • Non vuoi caricare il codice per tutti i tuoi riduttori in anticipo. Definire un modulo per alcuni riduttori e utilizzare DynamicModuleLoader e una libreria come reattiva per scaricare e aggiungere il modulo in fase di esecuzione.
  • Sono disponibili alcuni riduttori / middleware comuni che devono essere riutilizzati in diverse aree dell'applicazione. Definisci un modulo e includilo facilmente in quelle aree.
  • Hai un repository mono che contiene più applicazioni che condividono uno stato simile. Crea un pacchetto contenente alcuni moduli e riutilizzali nelle tue applicazioni

0

Ecco un altro esempio con i negozi di suddivisione del codice e redux, a mio avviso piuttosto semplici ed eleganti. Penso che possa essere abbastanza utile per coloro che sono alla ricerca di una soluzione funzionante.

Questo negozio è un po 'semplificato e non ti obbliga ad avere uno spazio dei nomi (riduttore.nome) nel tuo oggetto di stato, ovviamente potrebbe esserci una collisione con i nomi ma puoi controllarlo creando una convenzione di denominazione per i tuoi riduttori e dovrebbe andare bene.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.