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


189

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

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

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

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

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

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

Ответы:


245

Обновление: посмотрите также, как это делает 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);
      })
    }
  };

  // ...
}

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


13
Мне бы очень хотелось, чтобы этот тип функциональности был добавлен в проект. Возможность динамического добавления редукторов является обязательной при работе с разделением кода и большими приложениями. У меня есть целые поддеревья, к которым некоторые пользователи не могут получить доступ, и загрузка всех редукторов - пустая трата времени. Даже с игнорированием избыточности большие приложения могут действительно составлять редукторы.
ДжеффБаумгардт

2
Иногда «оптимизировать» что-то несущественное - большая трата.
XML

1
Надеюсь, приведенный выше комментарий имеет смысл ... как я выбежал из комнаты. Но в принципе я не вижу простого способа объединить редукторы в одну ветку в нашем дереве состояний, когда они динамически загружаются с разных маршрутов, /homepageа затем загружается большая часть этой ветки, когда пользователь переходит к их profile.примеру. Пример того, как сделать это, было бы здорово. В противном случае мне будет трудно выровнять свое дерево состояний, или у меня будут очень специфические имена ветвей user-permissionsиuser-personal
BryceHayden

1
И как мне действовать, если у меня начальное состояние?
Стальсо

3
В шаблоне github.com/mxstbr/react-boilerplate используется тот же метод, что и здесь, для редукторов нагрузки.
Pouya Sanooei

25

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

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

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


Спасибо @jonny, просто один на один, пример сейчас выдает ошибку.
Джейсон Дж. Натан

Объявление createReducer () отсутствует в вашем ответе (я знаю, что оно есть в ответе Дана Абрахамова, но я думаю, что в том числе это поможет избежать путаницы)
Packet Tracer

6

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

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

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

  2. Используйте createInjectStore из redux-инжектора вместо createStore из 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:


2

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

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

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

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

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

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

Характеристики

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

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

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

0

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.