Динамически уменьшающие нагрузку редукторы с реагирующим маршрутизатором 4
Я разбиваю свой код на основе компонентов, и я хочу вводить свои редукторы только тогда, когда компонент загружается, а не складывает их все с самого начала в магазине.
В ответном маршрутизаторе 3 это было довольно прямолинейно, но я не могу заставить его работать с реагирующим маршрутизатором 4.
Здесь редукторы и магазин:
reducers.js
import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'
import modalReducer from '../modules/modal'
export default combineReducers({
routing : routerReducer,
modal : modalReducer
})
store.js
import { createStore, applyMiddleware, compose } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import createHistory from 'history/createBrowserHistory'
import rootReducer from './reducers'
export const history = createHistory()
const initialState = {}
const enhancers = []
const middleware = [
thunk,
routerMiddleware(history)
]
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.devToolsExtension
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
)
const store = createStore(
rootReducer(),
initialState,
composedEnhancers
)
export default store
И я использую ленивую нагрузку для маршрутов.
Как реализовать раздельные редукторы?
Я хотел бы добавить асинхронные редукторы примерно так:
function createReducer(asyncReducers) {
return combineReducers({
...asyncReducers,
system,
router,
})
}
function injectReducer(store, { key, reducer }) {
if (Reflect.has(store.asyncReducers, key)) return
store.asyncReducers[key] = reducer
store.replaceReducer(createReducer(store.asyncReducers))
}
Ответы
Ответ 1
В реакционном маршрутизаторе v4 для асинхронной инъекции редукторов выполните следующие действия:
В файле reducer.js добавьте функцию createReducer, которая принимает injectedReducers как arg и возвращает объединенный редуктор:
/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers) {
return combineReducers({
route: routeReducer,
modal: modalReducer,
...injectedReducers,
});
}
Затем, в вашем файле store.js,
import createReducer from './reducers.js';
const store = createStore(
createReducer(),
initialState,
composedEnhancers
);
store.injectedReducers = {}; // Reducer registry
Теперь, чтобы инжектировать редуктор в асинхронном режиме, когда ваш реактивный контейнер монтируется, вам необходимо использовать функцию injectReducer.js в вашем контейнере, а затем составить все редукторы вместе с подключением. Пример компонента Todo.js:
// example component
import { connect } from 'react-redux';
import { compose } from 'redux';
import injectReducer from 'filepath/injectReducer';
import { addToDo, starToDo } from 'containers/Todo/reducer';
class Todo extends React.Component {
// your component code here
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const addToDoReducer = injectReducer({
key: 'todoList',
reducer: addToDo,
});
const starToDoReducer = injectReducer({
key: 'starredToDoList',
reducer: starToDo,
});
export default compose(
addToDoReducer,
starToDoReducer,
withConnect,
)(Todo);
React-Boilerplate - отличный источник для понимания всей этой установки. Вы можете создать пример приложения за считанные секунды. Код для injectReducer.js, configureStore.js (или store.js в вашем случае), и на самом деле вся эта конфигурация может быть взята из реакционного шаблона. Конкретную ссылку можно найти здесь для injectReducer.js, configureStore.js.
Ответ 2
Чтобы асинхронно вводить редукторы, на первом этапе вам нужно написать создать хранилище в указанном вами формате:
Переходники
В редукторах единственная разница заключается в том, чтобы получить asyncReducers в качестве входа функции createReducer и использовать его следующим образом для комбинированных редукторов.
function createReducer(asyncReducers) {
return combineReducers({
...asyncReducers,
system,
router,
})
}
Настроить хранилище
Файл configureStore должен выглядеть следующим образом. Я внес несколько изменений в вашу структуру. Сначала я применил middlewares в энхансерах, чтобы иметь возможность использовать хром-редукцию DevTool Extention, если он установлен в противном случае, использовать состав редукса (а также использовать редуктор редуктора для асинхронных редукторов).
import { createStore, applyMiddleware, compose } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import createHistory from 'history/createBrowserHistory'
import rootReducer from './reducers'
export const history = createHistory()
const initialState = {}
const middleware = [
thunk,
routerMiddleware(history)
]
const enhancers = [
applyMiddleware(...middlewares),
];
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// TODO Try to remove when 'react-router-redux' is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
// Prevent recomputing reducers for 'replaceReducer'
shouldHotReload: false,
})
: compose;
/* eslint-enable */
const store = createStore(
rootReducer(),
initialState,
composeEnhancers(...enhancers)
);
// Extensions
store.injectedReducers = {}; // Reducer registry
/ Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
export default store;
Составная часть
Простой компонент будет таким. Как вы видите в этом компоненте, мы сначала connect
компонент к реакции-redux и можем использовать mapStateToProps
и mapDispatchToProps
, а затем, чтобы ввести редуктор для этого файла, нам нужны две вещи:
1) файл редуктора, 2) включить функцию редуктора
после этого мы составляем соединение и редуктор, введенные в компонент.
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import reducerForThisComponent from './reducer';
import injectReducer from 'path_to_recuer_injector';
const Component = (props)=><div>Component</div>
function mapStateToProps (state){
return {}
}
const withConnect = connect(mapStateToProps);
const withReducer = injectReducer({ key: 'login', reducerForThisComponent });
export default compose(
withReducer,
withConnect,
)(Component);
injectReducer.js
этот файл может быть реализован несколькими способами. одна из лучших практик реализована реакционно-шаблонной плитой. Это файл, который используется для ввода редукторов в ваши компоненты; однако этот файл имеет еще одну зависимость (getInjectors.js
), которая может быть помещена в utils вместе с injectReducer.js
import React from 'react';
import PropTypes from 'prop-types';
import hoistNonReactStatics from 'hoist-non-react-statics';
import getInjectors from './getInjectors';
/**
* Dynamically injects a reducer
*
* @param {string} key A key of the reducer
* @param {function} reducer A reducer that will be injected
*
*/
export default ({ key, reducer }) => (WrappedComponent) => {
class ReducerInjector extends React.Component {
static WrappedComponent = WrappedComponent;
static contextTypes = {
store: PropTypes.object.isRequired,
};
static displayName = 'withReducer(${(WrappedComponent.displayName || WrappedComponent.name || 'Component')})';
componentWillMount() {
const { injectReducer } = this.injectors;
injectReducer(key, reducer);
}
injectors = getInjectors(this.context.store);
render() {
return <WrappedComponent {...this.props} />;
}
}
return hoistNonReactStatics(ReducerInjector, WrappedComponent);
};
getInjectors.js
import invariant from 'invariant';
import isEmpty from 'lodash/isEmpty';
import isFunction from 'lodash/isFunction';
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';
import createReducer from '../reducers'; //The createStoreFile
/**
* Validate the shape of redux store
*/
function checkStore(store) {
const shape = {
dispatch: isFunction,
subscribe: isFunction,
getState: isFunction,
replaceReducer: isFunction,
runSaga: isFunction,
injectedReducers: isObject,
injectedSagas: isObject,
};
invariant(
conformsTo(store, shape),
'(app/utils...) injectors: Expected a valid redux store'
);
}
export function injectReducerFactory(store, isValid) {
return function injectReducer(key, reducer) {
if (!isValid) checkStore(store);
invariant(
isString(key) && !isEmpty(key) && isFunction(reducer),
'(app/utils...) injectReducer: Expected 'reducer' to be a reducer function'
);
// Check 'store.injectedReducers[key] === reducer' for hot reloading when a key is the same but a reducer is different
if (Reflect.has(store.injectedReducers, key) && store.injectedReducers[key] === reducer) return;
store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign
store.replaceReducer(createReducer(store.injectedReducers));
};
}
export default function getInjectors(store) {
checkStore(store);
return {
injectReducer: injectReducerFactory(store, true),
};
}
Теперь все установлено. У вас есть все функциональные возможности, такие как впрыск редуктора и даже поддержка загрузки редуктора горячего модуля на стадии разработки. Однако я предлагаю две вещи:
-
Это может быть отличная идея взглянуть на react-boilerplate
поскольку он предлагает множество отличных функций, реализованных с использованием лучших практик, ориентированных на широкомасштабные приложения.
-
Если вы планируете разделять код, это означает, что у вас будет приложение с проблемой масштабируемости. В результате я рекомендую не использовать decux-thunk и использовать саму саму саму. И самое лучшее решение заключается в том, чтобы Inject saga middlewares asynchronously
и выгружать файлы саги сразу же после того, как компонент не монтируется. Эта практика может улучшить ваше приложение несколькими способами.
Ответ 3
Вы можете вводить не только редукторы, но и саги, загружать страницы кусками и грамотно составлять свои компоненты со своими собственными css и активами (изображениями, значками), ни одна вещь, глобальная, все динамически привязано к приложению. В этом есть целая философия - атомный дизайн, и вот шаблон, который преследует аналогичную идею:
https://github.com/react-boilerplate/react-boilerplate
Я понимаю, что мой ответ - недостаточно полный ответ, но он может дать больше информации о следующих шагах.