如何在Redux应用程序中动态加载reducers进行代码拆分?


187

我要迁移到Redux。

我的应用程序包含很多部分(页面,组件),因此我想创建许多化简器。Redux的例子表明,我应该使用它combineReducers()来生成一个reducer。

另外,据我了解,Redux应用程序应具有一个存储,并且在应用程序启动后即会创建。创建商店时,我应该通过我的组合减速器。如果应用程序不是太大,这是有道理的。

但是,如果我构建多个JavaScript捆绑包怎么办?例如,应用程序的每个页面都有自己的捆绑软件。我认为在这种情况下,一个减速器组合不好。我浏览了Redux的源代码,发现了replaceReducer()功能。这似乎是我想要的。

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

这是一个好方法吗?

Answers:


245

更新:另请参阅Twitter的工作方式

这不是一个完整的答案,但应该可以帮助您入门。请注意,我并没有丢弃旧的减速器-我只是在组合列表中添加了新的减速器。我认为没有理由丢弃旧的reducer —即使在最大的应用程序中,您也不大可能拥有成千上万的动态模块,这是您可能希望断开应用程序中某些reducers的连接的原因。

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

  // ...
}

表达这一点可能有更整洁的方式-我只是在说明这个想法。


13
我很想看到这种功能添加到项目中。在处理代码拆分和大型应用程序时,必须具有动态添加reducer的功能。我有整个子树,有些用户可能无法访问,并且加载所有的reducer都是浪费。即使忽略redux,大型应用程序实际上也可以堆积化简。
JeffBaumgardt '16

2
有时,“优化”无关紧要的东西会浪费更大的钱。
XML

1
希望上面的评论是有道理的。。。但是,基本上,我看不到一种简单的方法,当从不同的路由动态加载化简器到状态树中的单个分支时/homepage,再组合化简,然后当用户转到他们profile.的分支时加载更多的分枝。为此,太棒了。否则,我将很难整理状态树,或者必须有非常特定的分支名称,user-permissions以及user-personal
BryceHayden

1
如果我有初始状态,该怎么办?
Stalso

3
github.com/mxstbr/react-boilerplate样板使用此处提到的完全相同的技术来加载减速器。
普亚·萨努埃伊

25

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

// 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
  }
}

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

// 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
}

这是使用此设置创建的基本实时示例及其来源:

它还涵盖了必要的配置,以便为所有减速器进行热重装。


感谢@jonny,请注意,该示例现在引发错误。
杰森·内森

您的答案中缺少createReducer()声明(我知道这是Dan Abrahamov的答案,但我认为包括它可以避免造成混淆)
Packet Tracer

6

现在有一个模块,可将注入的reducer添加到redux存储中。它称为Redux注入器

使用方法如下:

  1. 请勿组合减速器。而是像平常一样将它们放在函数的(嵌套)对象中,但不合并它们。

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

  3. 使用injectReducer注入新的reducer。

这是一个例子:

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

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



2

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

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

模块具有以下优点:

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

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

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

特征

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

示例方案

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

0

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

这个存储有点简化,它不会强制您在状态对象中有一个命名空间(reducer.name),当然可能与名称发生冲突,但是您可以通过为化简器及其命名规则来控制它应该没事。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.