使用 react router 4 动态加载 redux reducer
Posted
技术标签:
【中文标题】使用 react router 4 动态加载 redux reducer【英文标题】:Dynamically load redux reducers with react router 4 【发布时间】:2018-10-03 22:06:16 【问题描述】:我正在根据组件拆分我的代码,并且我只想在组件加载时注入我的减速器,而不是从一开始就将它们全部堆叠在商店中。
在 react 路由器 3 中它非常简单,但我似乎无法让它与 react 路由器 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))
【问题讨论】:
这种复杂设置的原因是什么?你的应用真的需要多态化简器吗?让它们都在同一个商店中可用,并为每个组件使用正确的,怎么样? 这在大型项目中会很快失控 @S.Schenk 你能拆分你的减速器吗?你还有什么问题吗?如果没有,请接受其中一个答案,以便其他人将来看到这个问题:) 【参考方案1】:在 react-router v4 中,对于 reducer 的异步注入,请执行以下操作:
在您的 reducer.js 文件中添加一个名为 createReducer 的函数,该函数将注入的 Reducer 作为 arg 并返回组合的 reducer:
/**
* 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
现在,为了在您的 react 容器挂载时以异步方式注入 reducer,您需要在容器中使用 injectReducer.js 函数,然后将所有 reducer 与 connect 组合起来。 示例组件 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 在您的情况下)的代码,实际上整个配置都可以从 react-boilerplate 中获取。可以在此处找到injectReducer.js、configureStore.js 的具体链接。
【讨论】:
【参考方案2】:为了异步注入reducer,第一步你需要按照你提到的格式编写create store:
减速器
在 reducer 中,唯一的区别是获取 asyncReducers 作为 createReducer 函数的输入,并按以下方式将其用于 combine reducer。
function createReducer(asyncReducers)
return combineReducers(
...asyncReducers,
system,
router,
)
配置商店
您的 configureStore 文件应如下所示。我对你的结构做了一些改变。首先,我在增强器中应用了中间件,以便能够使用 chrome redux DevTool Extention(如果已安装),否则使用 redux compose,(并且还使用 reducer hot-reloader 用于异步 reducer)。
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
组件添加到 react-redux,并且可以使用 mapStateToProps
和 mapDispatchToProps
,然后为了为这个文件注入 reducer,我们需要两件事:
1)reducer文件,2)注入reducer函数
之后,我们组合connect和reducerInjected到组件中。
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
这个文件可以通过多种方式实现。最佳实践之一是由 react-boilerplate 实现的。这是用于将 reducer 注入组件的文件;但是,该文件还有一个依赖项 (getInjectors.js
),可以与 injectReducer.js 一起放入 utils 中
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),
;
现在一切都设置好了,你拥有了所有的功能,比如reducer注入,甚至在开发阶段支持热模块reducer加载。但是,我强烈建议两件事:
查看 react-boilerplate
可能是个好主意,因为它提供了许多出色的功能,并通过专注于大型应用程序的最佳实践实现了。
如果您计划进行代码拆分,则意味着您的应用程序将存在可伸缩性问题。因此,我建议不要使用 redux-thunk 而使用 redux saga。最好的解决方案是Inject saga middlewares asynchronously
并在组件卸载后立即弹出 saga 文件。这种做法可以通过多种方式改进您的应用程序。
【讨论】:
【参考方案3】:您不仅可以注入 reducer,还可以注入 sagas,按块加载页面,并使用自己的 css 和资产(图像、图标)使您的组件真正以胜任的方式进行,没有任何全局性的东西可以动态附加到应用程序。关于它有一个完整的哲学 - atomic design,这是一个追求类似想法的样板:
https://github.com/react-boilerplate/react-boilerplate
我意识到我的答案不够完整,但它可能会为后续步骤提供更多思路。
【讨论】:
以上是关于使用 react router 4 动态加载 redux reducer的主要内容,如果未能解决你的问题,请参考以下文章
利用 React/Redux/React-Router 4/webpack 开发大型 web 项目时如何按需加载
从cookie异步加载身份验证令牌时,React react-router-dom私有路由不起作用