Как динамически загружать редукторы для разделения кода в приложении Redux?
Я перехожу в Redux.
Мое приложение состоит из множества частей (страниц, компонентов), поэтому я хочу создать множество редукторов. Примеры Redux показывают, что я должен использовать combineReducers()
для генерации одного редуктора.
Также, как я понимаю, приложение Redux должно иметь одно хранилище и создается после запуска приложения. Когда магазин создается, я должен передать мой комбинированный редуктор. Это имеет смысл, если приложение не слишком велико.
Но что, если я построю более одного пакета JavaScript? Например, каждая страница приложения имеет собственный пакет. Я думаю, что в этом случае один комбинированный редуктор не очень хорош. Я просмотрел источники Redux и нашел функцию replaceReducer()
. Кажется, я хочу.
Я мог бы создать комбинированный редуктор для каждой части моего приложения и использовать replaceReducer()
, когда я перемещаюсь между частями приложения.
Это хороший подход?
Ответы
Ответ 1
Это не полный ответ, но должен помочь вам начать. Обратите внимание, что я не выбрасываю старые редукторы - я просто добавляю новые в список комбинаций. Я не вижу смысла выбрасывать старые редукторы - даже в самом крупном приложении у вас вряд ли будут тысячи динамических модулей, и именно в этот момент вы можете отключить некоторые редукторы в своем приложении.
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.
Вот как это можно использовать:
Не комбинируйте редукторы. Вместо этого поместите их в (вложенный) объект функций, как обычно, но без их объединения.
Используйте createInjectStore из redux-injector вместо createStore from redux.
Введите новые редукторы с помощью 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 немного упрощен, он не заставляет вас иметь пространство имен (имя редуктора) в объекте состояния, конечно, может быть столкновение с именами, но вы можете контролировать это, создав соглашение об именах для своих редукторов, и это должно быть хорошо.