如何为Redux应用程序中的代码拆分动态加载reducers?

时间:2015-10-06 10:45:56

标签: javascript flux redux code-splitting

我要迁移到Redux。

我的应用程序包含很多部分(页面,组件),所以我想创建许多reducer。 Redux示例显示我应该使用combineReducers()生成一个reducer。

另外据我所知,Redux应用程序应该有一个存储,并且一旦应用程序启动就会创建它。在创建商店时,我应该通过我的组合减速机。如果应用程序不是太大,这是有道理的。

但是,如果我构建多个JavaScript包怎么办?例如,每个应用程序页面都有自己的包。我认为在这种情况下,一个联合减速器并不好。我查看了Redux的来源,并找到了replaceReducer()函数。这似乎是我想要的。

我可以为我的应用程序的每个部分创建组合的reducer,并在我在应用程序的各个部分之间移动时使用replaceReducer()

这是一个好方法吗?

6 个答案:

答案 0 :(得分:222)

  

更新:另请参阅how Twitter does it

这不是一个完整的答案,但应该可以帮助你开始。请注意,我不丢弃旧的Reducer - 我只是在组合列表中添加新的。我认为没有理由抛弃旧的Reducer - 即使在最大的应用程序中你也不可能拥有数千个动态模块,这就是可能想要断开应用程序中某些Reducer的地步。

reducers.js

import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';

export default function createReducer(asyncReducers) {
  return combineReducers({
    users,
    posts,
    ...asyncReducers
  });
}

store.js

import { createStore } from 'redux';
import createReducer from './reducers';

export default function configureStore(initialState) {
  const store = createStore(createReducer(), initialState);
  store.asyncReducers = {};
  return store;
}

export function injectAsyncReducer(store, name, asyncReducer) {
  store.asyncReducers[name] = asyncReducer;
  store.replaceReducer(createReducer(store.asyncReducers));
}

routes.js

import { injectAsyncReducer } from './store';

// Assuming React Router here but the principle is the same
// regardless of the library: make sure store is available
// when you want to require.ensure() your reducer so you can call
// injectAsyncReducer(store, name, reducer).

function createRoutes(store) {
  // ...

  const CommentsRoute = {
    // ...

    getComponents(location, callback) {
      require.ensure([
        './pages/Comments',
        './reducers/comments'
      ], function (require) {
        const Comments = require('./pages/Comments').default;
        const commentsReducer = require('./reducers/comments').default;

        injectAsyncReducer(store, 'comments', commentsReducer);
        callback(null, Comments);
      })
    }
  };

  // ...
}

可能有更简洁的表达方式 - 我只是在表达这个想法。

答案 1 :(得分:23)

这就是我在当前应用程序中实现它的方法(基于Dan在GitHub问题上的代码!)

// Based on https://github.com/rackt/redux/issues/37#issue-85098222
class ReducerRegistry {
  constructor(initialReducers = {}) {
    this._reducers = {...initialReducers}
    this._emitChange = null
  }
  register(newReducers) {
    this._reducers = {...this._reducers, ...newReducers}
    if (this._emitChange != null) {
      this._emitChange(this.getReducers())
    }
  }
  getReducers() {
    return {...this._reducers}
  }
  setChangeListener(listener) {
    if (this._emitChange != null) {
      throw new Error('Can only set the listener for a ReducerRegistry once.')
    }
    this._emitChange = listener
  }
}

在引导您的应用程序时创建一个注册表实例,传入将包含在条目包中的reducer:

// coreReducers is a {name: function} Object
var coreReducers = require('./reducers/core')
var reducerRegistry = new ReducerRegistry(coreReducers)

然后在配置商店和路由时,使用可以将reducer注册表赋予的函数:

var routes = createRoutes(reducerRegistry)
var store = createStore(reducerRegistry)

这些功能类似于:

function createRoutes(reducerRegistry) {
  return <Route path="/" component={App}>
    <Route path="core" component={Core}/>
    <Route path="async" getComponent={(location, cb) => {
      require.ensure([], require => {
        reducerRegistry.register({async: require('./reducers/async')})
        cb(null, require('./screens/Async'))
      })
    }}/>
  </Route>
}

function createStore(reducerRegistry) {
  var rootReducer = createReducer(reducerRegistry.getReducers())
  var store = createStore(rootReducer)

  reducerRegistry.setChangeListener((reducers) => {
    store.replaceReducer(createReducer(reducers))
  })

  return store
}

以下是使用此设置创建的基本实例,其来源为:

它还涵盖了为所有减速器启用热重新加载的必要配置。

答案 2 :(得分:6)

现在有一个模块可以将注入Reducer添加到redux存储中。它被称为Redux Injector。 https://github.com/randallknutson/redux-injector

以下是如何使用它:

  1. 不要组合减速器。而是将它们放在函数的(嵌套)对象中,而不是将它们组合在一起。

  2. 使用redux-injector中的createInjectStore而不是redux中的createStore。

  3. 使用injectReducer注入新的reducer。

  4. 以下是一个例子:

    import { createInjectStore, injectReducer } from 'redux-injector';
    
    const reducersObject = {
       router: routerReducerFunction,
       data: {
         user: userReducerFunction,
         auth: {
           loggedIn: loggedInReducerFunction,
           loggedOut: loggedOutReducerFunction
         },
         info: infoReducerFunction
       }
     };
    
    const initialState = {};
    
    let store = createInjectStore(
      reducersObject,
      initialState
    );
    
    // Now you can inject reducers anywhere in the tree.
    injectReducer('data.form', formReducerFunction);
    

    完全披露:我是模块的创建者。

答案 3 :(得分:4)

截至2017年10月:

  • Reedux

    实现Dan建议的内容,仅此而已,无需触及您的商店,项目或习惯

还有其他库,但它们可能包含太多依赖项,示例少,使用复杂,与某些中间件不兼容或要求您重写状态管理。从Reedux的介绍页面复制:

答案 4 :(得分:1)

我们发布了一个新库,该库有助于调制Redux应用程序,并允许动态添加/删除Reducers和中间件。

请看一看 https://github.com/Microsoft/redux-dynamic-modules

模块具有以下优点:

  • 模块可以轻松地在整个应用程序中或在多个相似的应用程序之间重复使用。

  • 组件声明了它们所需的模块,redux-dynamic-modules确保为该组件加载了模块。

  • 可以动态地从商店中添加/删除模块。当组件安装或用户执行操作时

功能

  • 将reducer,中间件和状态组合到一个可重用的模块中。
  • 随时从Redux存储中添加和删除模块。
  • 使用包含的组件在呈现组件时自动添加模块
  • 扩展可与流行的库集成,包括redux-saga和redux-observable

示例方案

  • 您不想预先为所有减速器加载代码。为某些reducer定义一个模块,并使用DynamicModuleLoader和诸如react-loadable之类的库在运行时下载和添加模块。
  • 您有一些通用的reducer /中间件,需要在应用程序的不同区域中重复使用。定义一个模块并将其轻松包含在这些区域中。
  • 您有一个包含多个共享相似状态的应用程序的单仓库。创建一个包含一些模块的程序包,然后在您的应用程序中重复使用它们

答案 5 :(得分:0)

这是另一个带有代码拆分和redux存储的example,非常简单&amp;在我看来优雅。我认为对于那些正在寻找有效解决方案的人来说,这可能非常有用。

这个store有点简化,它不会强迫你在你的状态对象中有一个命名空间(reducer.name),当然可能会与名称发生冲突但你可以通过创建一个名称来控制它减速器的命名约定,应该没问题。