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

IT技术 javascript flux redux code-splitting
2021-02-09 22:16:24

我要迁移到 Redux。

我的应用程序由很多部分(页面、组件)组成,所以我想创建许多减速器。Redux 示例表明我应该使用它combineReducers()来生成一个减速器。

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

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

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

这是一个好方法吗?

6个回答

更新:另请参阅Twitter 如何做到这一点

这不是一个完整的答案,但应该可以帮助您入门。请注意,我并没有扔掉旧的减速器——我只是将新的减速器添加到组合列表中。我认为没有理由丢弃旧的 reducer——即使在最大的应用程序中,您也不可能拥有数千个动态module,这就是您可能希望断开应用程序中某些 reducer 的地方。

减速器.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
  });
}

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

路由.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);
      })
    }
  };

  // ...
}

可能有更简洁的表达方式——我只是展示这个想法。

github.com/mxstbr/react-boilerplate样板使用这里提到的完全相同的技术来加载减速器。
2021-03-14 22:16:24
如果我有初始状态,我应该如何行动?
2021-03-18 22:16:24
希望上面的评论是有道理的......因为我没有空间了。但基本上我没有看到一种简单的方法,当它们从不同的路由动态加载时,将 reducer 组合到我们状态树上的单个分支中/homepage,然后当用户转到他们profile.时候,更多的分支被加载。这样做,会很棒。否则我很难把我的状态树弄平,或者我必须有非常具体的分支名称user-permissionsuser-personal
2021-03-19 22:16:24
我很想看到这种类型的功能添加到项目中。在处理代码拆分和大型应用程序时,动态添加reducer 的能力是必须的。我有一些用户可能无法访问的整个子树,加载所有的减速器是一种浪费。即使使用 redux-ignore 大型应用程序也可以真正堆叠减速器。
2021-03-21 22:16:24
有时,“优化”一些无关紧要的东西是一种更大的浪费。
2021-04-11 22:16:24

这就是我在当前应用程序中实现它的方式(基于 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
  }
}

在引导您的应用程序时创建一个注册表实例,传入将包含在入口包中的减速器:

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

然后在配置 store 和路由时,使用一个函数,你可以给 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
}

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

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

您的答案中缺少 createReducer() 声明(我知道它在 Dan Abrahamov 的答案中,但我认为包括它会避免混淆)
2021-03-24 22:16:24
谢谢@jonny,提醒一下,这个例子现在抛出一个错误。
2021-04-04 22:16:24

现在有一个module可以将注入的 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);

完全披露:我是该module的创建者。

截至 2017 年 10 月:

  • 芦苇

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

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

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

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

module提供以下好处:

  • module可以在整个应用程序中或在多个相似的应用程序之间轻松重用。

  • 组件声明它们需要的module,而 redux-dynamic-modules 确保为组件加载module。

  • module可以动态地从存储中添加/删除,例如。当组件安装或用户执行操作时

特征

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

示例场景

  • 您不想预先加载所有减速器的代码。为一些减速器定义一个module,并使用 DynamicModuleLoader 和像 react-loadable 这样的库在运行时下载和添加你的module。
  • 您有一些常见的减速器/中间件,需要在应用程序的不同区域中重复使用。定义一个module并将其轻松包含在这些区域中。
  • 您有一个包含多个共享相似状态的应用程序的单一存储库。创建一个包含一些module的包并在您的应用程序中重复使用它们