使用 react 路由器 4 动态加载 redux 缩减器



我正在根据组件拆分我的代码,我想仅在组件加载时才注入我的化简器,而不是从头开始将它们堆叠在存储中。

在反应路由器3 中,这非常简单,但我似乎无法让它与反应路由器 4 一起使用。

这是减速器和商店:

减速器.js

import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'
import modalReducer from '../modules/modal'
export default combineReducers({
routing : routerReducer,
modal   : modalReducer
})

商店.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))
}

react-router v4中,对于化简器的异步注入,请执行以下操作:

在你的化简器中.js文件添加一个名为createReducer的函数,该函数将注入的Reducer作为arg并返回组合的化简器:

/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers) {
return combineReducers({
route: routeReducer,
modal: modalReducer,
...injectedReducers,
});
} 

然后,在您的商店.js文件中,

import createReducer from './reducers.js';
const store = createStore(
createReducer(),
initialState,
composedEnhancers
);
store.injectedReducers = {}; // Reducer registry

现在,为了在 react 容器挂载时以异步方式注入化简器,您需要在容器中使用 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在你的例子中)的代码,实际上整个配置可以从react-样板中获取。具体链接可以在这里找到injectReducer.js,configureStore.js。

为了异步注入化简器,在第一步中,您需要以您提到的格式编写创建存储:

减速器

在化简器中,唯一的区别是获取异步化简器作为createReducer函数的输入,并按以下方式将其用于组合化简器。

function createReducer(asyncReducers) {
return combineReducers({
...asyncReducers,
system,
router,
})
}

配置应用商店

您的配置存储文件应如下所示。我对你的结构做了一些改变。首先,我在增强器中应用了中间件,以便能够在安装时使用chrome redux DevTool Extensionion,否则使用redux compose(并且还使用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;

元件

一个简单的组件将是这样的。正如您在此组件中看到的,我们首先将组件connectreact-redux 并且可以使用mapStateToPropsmapDispatchToProps,然后为了为此文件注入化简器,我们需要两件事:

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

喷油减速器.js

这个文件可以通过很多方式实现。 最佳实践之一是由 React-Boilerplate 实现的。这是用于将减速器注入组件的文件;但是,这个文件还有一个依赖关系(getInjectors.js),可以和injectReducer一起放在一个utils中.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);
};

得到喷油器.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),
};
}

现在一切都准备好了,您拥有减速器注入等所有功能,甚至在开发阶段支持热模块减速器负载。但是,我强烈建议两件事:

  1. 查看react-boilerplate可能是一个好主意,因为它提供了许多通过专注于大规模应用程序的最佳实践实现的强大功能。

  2. 如果您计划进行代码拆分,则意味着您将拥有一个具有可伸缩性问题的应用程序。因此,我建议不要使用 redux-thunk,而是使用 redux saga。最好的解决方案是在组件卸载后立即Inject saga middlewares asynchronously并弹出 saga 文件。这种做法可以通过多种方式改进您的应用程序。

您不仅可以注入化简器,还可以注入sagas,逐块加载页面,并使用自己的css和资产(图像,图标)使您的组件真正以有效的方式进行,没有任何全局一切都动态附加到应用程序。关于它有一个完整的哲学 - 原子设计,这里有一个追求类似想法的样板:

https://github.com/react-boilerplate/react-boilerplate

我意识到我的答案不够完整,但它可能会为下一步提供更多想法。

最新更新