使用react-router 4 动态加载 redux 减速器

IT技术 reactjs redux react-router
2021-05-02 13:37:06

我正在根据组件拆分我的代码,并且我只想在组件加载时注入我的减速器,而不是从一开始就在商店中将它们全部堆叠起来。

在react-router 3 中,它非常简单,但我似乎无法让它与react-router 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))
}
3个回答

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

在您的reducer.js文件中添加一个名为createReducer 的函数,该函数将注入的Reducer 作为参数并返回组合的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.jsconfigureStore.js 的特定链接

为了异步注入reducers,第一步你需要按照你提到的格式编写create store:

减速机

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

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

配置商店

您的 configureStore 文件应如下所示。我对您的结构进行了一些更改。首先,我在增强器中应用了中间件,以便能够使用 chrome redux DevTool Extention,如果安装了它,否则使用 redux compose,(并且还使用 reducer hot-reloader 来实现异步减速器)。

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 并且可以使用mapStateToPropsand 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);

注入Reducer.js

该文件可以通过多种方式实现。最佳实践之一是通过 react-boilerplate 实现的。这是用于将减速器注入组件的文件;然而,这个文件还有一个依赖项 ( 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注入,甚至在开发阶段支持热modulereducer加载。但是,我强烈建议两件事:

  1. 考虑一下这可能是一个好主意,react-boilerplate因为它提供了许多出色的功能,这些功能是通过专注于大型应用程序的最佳实践实现的。

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

您不仅可以注入 reducer,还可以注入 saga,按块加载页面,并使用自己的 css 和资产(图像、图标)真正以称职的方式使您的组件成为全球性的,一切都动态附加到应用程序。有一个关于它的整体哲学 -原子设计,这里有一个追求类似想法的样板:

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

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