Как динамически загружать редукторы для разделения кода в приложении Redux?

Я собираюсь мигрировать в Redux.

мое приложение состоит из множества частей (страниц, компонентов), поэтому я хочу создать много редукторов. Примеры Redux показывают, что я должен использовать combineReducers() для создания одного редуктора.

также, как я понимаю, приложение Redux должно иметь один магазин, и он создается после запуска приложения. Когда магазин создается, я должен передать свой комбинированный редуктор. Это имеет смысл если приложение не слишком большой.

но что, если Я создаю более одного пакета JavaScript? Например, каждая страница приложения имеет собственный набор. Я думаю, что в этом случае один комбинированный редуктор не хорошо. Я просмотрел источники Redux и я нашел

5 ответов


обновление: см. также как Twitter делает это.

это не полный ответ, но должны помочь вам начать работу. Обратите внимание, что я не выбрасывать старые редукторы - Я просто добавляю новые в список комбинаций. Я не вижу причин выбрасывать старые редукторы-даже в самом большом приложении у вас вряд ли будут тысячи динамических модулей, в которых вы может хочу отключить некоторые редукторы в приложение.

редукторы.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
  });
}

магазине.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));
}

маршруты.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);
      })
    }
  };

  // ...
}

может быть более аккуратный способ выразить это-я просто показываю идею.


вот как я реализовал его в текущем приложении (на основе кода Дэна из проблемы 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
  }
}

создайте экземпляр реестра при загрузке вашего приложения, передавая редукторы, которые будут включены в набор записей:

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

затем при настройке магазина и маршрутов используйте функцию, которую вы можете дать реестру редуктора:

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

где эти функции выглядят примерно так:

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
}

вот основные живой пример, который был создан с этой настройкой, и его источник:

оно также покрывает необходимую конфигурацию для того чтобы включить горячий перезаряжать для всех ваших редукторов.


теперь есть модуль, который добавляет инъекционные редукторы в хранилище redux. Оно вызван инжектором Redux. https://github.com/randallknutson/redux-injector

вот как его использовать:

  1. не комбинировать редукторы. Вместо этого поместите их в (вложенный) объект функций, как обычно, но без их объединения.

  2. используйте createInjectStore из redux-инжектора вместо createStore из возвращение.

  3. впрысните новые редукторы с injectReducer.

вот пример:

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

полное раскрытие: я создатель модуля.


по состоянию на октябрь 2017:

  • Reedux

    реализует то, что предложил Дэн, и ничего больше, не касаясь вашего магазина, вашего проекта или ваших привычек

есть и другие библиотеки, но они могут иметь слишком много зависимостей, меньше примеров, сложное использование, несовместимы с некоторыми промежуточными программами или требуют переписать управление состоянием. Скопировано со страницы ввода Reedux:


вот еще пример с разделением кода и магазинами redux, довольно просто и элегантно, на мой взгляд. Я думаю, что это может быть очень полезно для тех, кто ищет рабочее решение.

этой магазине немного упрощено, это не заставляет вас иметь пространство имен (редуктор.имя) в вашем объекте состояния, конечно, может быть столкновение с именами, но вы можете контролировать это, создавая соглашение об именах для ваших редукторов, и оно должно быть штраф.