Wie lade ich Reduzierer für die Code-Aufteilung in einer Redux-Anwendung dynamisch?

187

Ich werde zu Redux migrieren.

Meine Anwendung besteht aus vielen Teilen (Seiten, Komponenten), daher möchte ich viele Reduzierungen erstellen. Redux-Beispiele zeigen, dass ich combineReducers()einen Reduzierer generieren sollte .

Soweit ich weiß, sollte die Redux-Anwendung einen Speicher haben und wird erstellt, sobald die Anwendung gestartet wird. Wenn das Geschäft erstellt wird, sollte ich meinen kombinierten Reduzierer übergeben. Dies ist sinnvoll, wenn die Anwendung nicht zu groß ist.

Aber was ist, wenn ich mehr als ein JavaScript-Bundle erstelle? Beispielsweise verfügt jede Anwendungsseite über ein eigenes Bundle. Ich denke in diesem Fall ist der eine kombinierte Reduzierer nicht gut. Ich habe die Quellen von Redux durchgesehen und eine replaceReducer()Funktion gefunden . Es scheint das zu sein, was ich will.

Ich könnte für jedes Teil meiner Anwendung einen kombinierten Reduzierer erstellen und verwenden, replaceReducer()wenn ich zwischen Teilen der Anwendung wechsle.

Ist das ein guter Ansatz?

Pavel Teterin
quelle

Antworten:

245

Update: Sehen Sie auch, wie Twitter es macht .

Dies ist keine vollständige Antwort, sollte Ihnen jedoch den Einstieg erleichtern. Beachten Sie, dass ich alte Reduzierstücke nicht wegwerfe - ich füge nur neue zur Kombinationsliste hinzu. Ich sehe keinen Grund, die alten Reduzierungen wegzuwerfen - selbst in der größten App ist es unwahrscheinlich, dass Sie Tausende von dynamischen Modulen haben. Dies ist der Punkt, an dem Sie möglicherweise einige Reduzierungen in Ihrer Anwendung trennen möchten.

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

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

  // ...
}

Es gibt vielleicht eine bessere Art, dies auszudrücken - ich zeige nur die Idee.

Dan Abramov
quelle
13
Ich würde gerne sehen, wie diese Art von Funktionalität dem Projekt hinzugefügt wird. Die Möglichkeit, Reduzierer dynamisch hinzuzufügen, ist ein Muss, wenn es um die Aufteilung von Code und große Anwendungen geht. Ich habe ganze Unterbäume, auf die einige Benutzer möglicherweise nicht zugreifen können, und das Laden aller Reduzierungen ist eine Verschwendung. Selbst mit Redux-Ignorieren können große Anwendungen Reduzierungen wirklich stapeln.
JeffBaumgardt
2
Manchmal ist es eine größere Verschwendung, etwas Unwichtiges zu optimieren.
XML
1
Hoffentlich macht der obige Kommentar Sinn ... da mir der Raum ausgegangen ist. Aber im Grunde sehe ich keine einfache Möglichkeit, die Reduzierer in einem einzigen Zweig in unserem Statusbaum zu kombinieren, wenn sie dynamisch von verschiedenen Routen geladen werden, /homepageund dann wird mehr von diesem Zweig geladen, wenn der Benutzer zu ihrem profile.Beispiel geht das zu tun, wäre großartig. Ansonsten fällt es mir schwer, meinen Staatsbaum zu verflachen, oder ich muss sehr spezifische user-permissionsuser-personal
Zweignamen
1
Und wie soll ich handeln, wenn ich einen Ausgangszustand habe?
Stalso
3
github.com/mxstbr/react-boilerplate boilerplate verwendet genau die hier erwähnte Technik zum Laden von Reduzierstücken.
Pouya Sanooei
25

So habe ich es in einer aktuellen App implementiert (basierend auf Code von Dan aus einem GitHub-Problem!)

// 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
  }
}

Erstellen Sie beim Booten Ihrer App eine Registrierungsinstanz und übergeben Sie Reduzierungen, die im Eintragspaket enthalten sind:

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

Verwenden Sie dann beim Konfigurieren des Speichers und der Routen eine Funktion, der Sie die Reduzierungsregistrierung zuweisen können:

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

Wo diese Funktionen ungefähr so ​​aussehen:

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
}

Hier ist ein grundlegendes Live-Beispiel, das mit diesem Setup erstellt wurde, und seine Quelle:

Es enthält auch die erforderliche Konfiguration, um das Hot-Reload für alle Ihre Reduzierungen zu ermöglichen.

Jonny Buchanan
quelle
Danke @jonny, nur ein Kopf hoch, das Beispiel wirft jetzt einen Fehler.
Jason J. Nathan
createReducer () Deklaration fehlt in Ihrer Antwort (ich weiß, dass es in Dan Abrahamovs Antwort ist, aber ich denke, es würde Verwirrung vermeiden)
Packet Tracer
6

Es gibt jetzt ein Modul, das dem Redux-Speicher injizierende Reduzierstücke hinzufügt. Es heißt Redux Injector .

So verwenden Sie es:

  1. Reduzierstücke nicht kombinieren. Fügen Sie sie stattdessen wie gewohnt in ein (verschachteltes) Objekt von Funktionen ein, ohne sie jedoch zu kombinieren.

  2. Verwenden Sie createInjectStore von Redux-Injector anstelle von createStore von Redux.

  3. Injizieren Sie neue Reduzierstücke mit InjectReducer.

Hier ist ein Beispiel:

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

Vollständige Offenlegung: Ich bin der Schöpfer des Moduls.

Randall Knutson
quelle
4

Stand Oktober 2017:

  • Reedux

    implementiert, was Dan vorgeschlagen hat und nichts weiter, ohne Ihren Laden, Ihr Projekt oder Ihre Gewohnheiten zu berühren

Es gibt auch andere Bibliotheken, aber sie haben möglicherweise zu viele Abhängigkeiten, weniger Beispiele, komplizierte Verwendung, sind mit einigen Middlewares nicht kompatibel oder erfordern, dass Sie Ihre Statusverwaltung neu schreiben. Von der Intro-Seite von Reedux kopiert:

Silviu-Marian
quelle
2

Wir haben eine neue Bibliothek veröffentlicht, die beim Modulieren einer Redux-App hilft und das dynamische Hinzufügen / Entfernen von Reduzierern und Middlewares ermöglicht.

Bitte werfen Sie einen Blick auf https://github.com/Microsoft/redux-dynamic-modules

Module bieten folgende Vorteile:

  • Module können problemlos in der gesamten Anwendung oder zwischen mehreren ähnlichen Anwendungen wiederverwendet werden.

  • Komponenten deklarieren die von ihnen benötigten Module und Redux-Dynamic-Module stellen sicher, dass das Modul für die Komponente geladen wird.

  • Module können dynamisch zum Geschäft hinzugefügt / daraus entfernt werden, z. wenn eine Komponente bereitgestellt wird oder wenn ein Benutzer eine Aktion ausführt

Eigenschaften

  • Gruppieren Sie Reduzierer, Middleware und Status in einem einzigen wiederverwendbaren Modul.
  • Hinzufügen und Entfernen von Modulen zu einem Redux-Speicher jederzeit.
  • Verwenden Sie die enthaltene Komponente, um beim Rendern einer Komponente automatisch ein Modul hinzuzufügen
  • Erweiterungen ermöglichen die Integration in gängige Bibliotheken, einschließlich Redux-Saga und Redux-Observable

Beispielszenarien

  • Sie möchten den Code nicht für alle Ihre Reduzierungen im Voraus laden. Definieren Sie ein Modul für einige Reduzierungen und verwenden Sie DynamicModuleLoader und eine Bibliothek wie React-Loadable, um Ihr Modul zur Laufzeit herunterzuladen und hinzuzufügen.
  • Sie haben einige gängige Reduzierungen / Middleware, die in verschiedenen Bereichen Ihrer Anwendung wiederverwendet werden müssen. Definieren Sie ein Modul und fügen Sie es einfach in diese Bereiche ein.
  • Sie haben ein Mono-Repo, das mehrere Anwendungen enthält, die einen ähnlichen Status haben. Erstellen Sie ein Paket mit einigen Modulen und verwenden Sie sie in Ihren Anwendungen wieder
Code Ninja
quelle
0

Hier ist ein weiteres Beispiel mit Code-Splitting und Redux-Stores, meiner Meinung nach ziemlich einfach und elegant. Ich denke, es kann sehr nützlich sein für diejenigen, die nach einer funktionierenden Lösung suchen.

Dieser Speicher ist etwas vereinfacht. Er zwingt Sie nicht dazu, einen Namespace (reducer.name) in Ihrem Statusobjekt zu haben. Natürlich kann es zu einer Kollision mit Namen kommen, aber Sie können dies steuern, indem Sie eine Namenskonvention für Ihre Reduzierer erstellen sollte gut sein.

Maksym Oliinyk
quelle