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

Я перехожу в Redux.

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

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

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

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

Это хороший подход?

Ответ 1

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

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

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

  // ...
}

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

Ответ 2

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

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

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

Ответ 3

Теперь есть модуль, который добавляет инъекционные редукторы в хранилище редуксов. Он называется Redux Injector.

Вот как это можно использовать:

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

  2. Используйте createInjectStore из redux-injector вместо createStore from redux.

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

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

Ответ 4

По состоянию на октябрь 2017 года:

  • Reedux

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

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

Ответ 5

Мы выпустили новую библиотеку, которая помогает модулировать приложение Redux и позволяет динамически добавлять/удалять Редукторы и промежуточное ПО.

Пожалуйста, посмотрите на https://github.com/Microsoft/redux-dynamic-modules

Модули предоставляют следующие преимущества:

  • Модули могут быть легко использованы повторно в приложении или между несколькими похожими приложениями.

  • Компоненты объявляют необходимые им модули, а redux-dynamic-modules гарантирует, что модуль загружен для компонента.

  • Модули могут быть добавлены/удалены из магазина динамически, напр. когда компонент монтируется или когда пользователь выполняет действие

Особенности

  • Сгруппируйте редукторы, промежуточное ПО и состояние в один модуль, который можно многократно использовать.
  • Добавляйте и удаляйте модули из хранилища Redux в любое время.
  • Используйте включенный компонент для автоматического добавления модуля при визуализации компонента
  • Расширения обеспечивают интеграцию с популярными библиотеками, включая redux-saga и redux-observable

Примеры сценариев

  • Вы не хотите загружать код для всех ваших редукторов заранее. Определите модуль для некоторых редукторов и используйте DynamicModuleLoader и такую библиотеку, как реагирующая загрузка для загрузки и добавления вашего модуля во время выполнения.
  • У вас есть общие редукторы/промежуточное ПО, которые необходимо повторно использовать в различных областях вашего приложения. Определите модуль и легко включите его в эти области.
  • У вас есть моно-репо, содержащее несколько приложений, которые имеют одинаковое состояние. Создайте пакет, содержащий несколько модулей, и повторно используйте их в своих приложениях.

Ответ 6

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

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