首页 文章

使用反应路由器4动态加载redux减速器

提问于
浏览
12

我正在基于组件拆分我的代码,我只想在组件加载时注入我的reducer,而不是从商店的开头堆叠它们 .

在反应路由器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

我正在使用延迟加载路线 .

如何实施拆分减速器?

我想注入async redurs之类的东西:

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 回答

  • 0

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

    在你的 reducer.js 文件中添加一个名为createReducer的函数,它将injectReducers作为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
    

    现在,为了在反应容器安装时以异步方式注入reducer,您需要在容器中使用injectReducer.js函数,然后将所有reducers与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 .

  • 11

    为了异步注入reducer,在第一步中,您需要以您提到的格式编写create store:

    Reducers

    在reducers中,唯一的区别是将asyncReducers作为createReducer函数的输入,并以下列方式使用它来组合reducers .

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

    Configure Store

    您的configureStore文件应如下所示 . 我对你的结构做了一些改动 . 首先,我在增强器中应用了中间件,以便能够使用chrome redux DevTool Extention(如果已安装),否则使用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;
    

    Component

    一个简单的组件就是这样的 . 正如您在此组件中看到的那样,我们首先使用 connect 组件来响应还原,并且可以使用 mapStateToPropsmapDispatchToProps ,然后为了为此文件注入reducer,我们需要两件事:

    1)reducer文件,2)注入reducer功能

    之后,我们组成connect和reducerInjection到组件 .

    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

    这个文件可以通过很多方式实现 . 反应 - 样板实现了最佳实践之一 . 这是用于将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),
      };
    }
    

    现在一切都已设定,您拥有所有功能,例如减速器喷射,甚至支持开发阶段的热模块减速器负载 . 但是,我强烈建议两件事:

    • 查看 react-boilerplate 可能是个好主意,因为它提供了许多以大型应用程序为重点的最佳实践实现的强大功能 .

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

  • 0

    你不仅可以注入减速器,还可以注入sagas,按块加载页面,并使用自己的css和资产(图像,图标)使你的组件真正处于干练的状态 . 没有什么全局的东西都动态地附加到应用程序 . 关于它有一个完整的哲学 - atomic design,这是一个追求类似想法的样板:

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

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

相关问题