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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32968016/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 14:27:08  来源:igfitidea点击:

How to dynamically load reducers for code splitting in a Redux application?

javascriptfluxreduxcode-splitting

提问by Pavel Teterin

I'm going migrate to Redux.

我要迁移到 Redux。

My application consists of a lot of parts (pages, components) so I want to create many reducers. Redux examples show that I should use combineReducers()to generate one reducer.

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

Also as I understand Redux application should have one store and it is created once the application starts. When the store is being created I should pass my combined reducer. This makes sense if the application is not too big.

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

But what if I build more than one JavaScript bundle? For example, each page of application has own bundle. I think in this case the one combined reducer is not good. I looked through the sources of Redux and I have found replaceReducer()function. It seems to be what I want.

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

I could create combined reducer for each part my application and use replaceReducer()when I move between parts of application.

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

Is this a good approach?

这是一个好方法吗?

回答by Dan Abramov

Update: see also how Twitter does it.

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

This is not a full answer but should help you get started. Note that I'm not throwing away old reducers—I'm just adding new ones to the combination list. I see no reason to throw away the old reducers—even in the largest app you're unlikely to have thousands of dynamic modules, which is the point where you mightwant to disconnect some reducers in your application.

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

reducers.js

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

商店.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

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

  // ...
}

There may be neater way of expressing this—I'm just showing the idea.

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

回答by Jonny Buchanan

This is how I implemented it in a current app (based on code by Dan from a GitHub issue!)

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

Create a registry instance when bootstrapping your app, passing in reducers which will be included in the entry bundle:

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

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

Then when configuring the store and routes, use a function which you can give the reducer registry to:

然后在配置 store 和路由时,使用一个函数,你可以给 reducer 注册表:

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

Where these functions look something like:

这些函数看起来像:

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
}


Here's a basic live example which was created with this setup, and its source:

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

It also covers the necessary configuration to enable hot reloading for all your reducers.

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

回答by Randall Knutson

There is now a module that adds injecting reducers into the redux store. It is called Redux Injector.

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

Here is how to use it:

以下是如何使用它:

  1. Do not combine reducers. Instead put them in a (nested) object of functions as you would normally but without combining them.

  2. Use createInjectStore from redux-injector instead of createStore from redux.

  3. Inject new reducers with injectReducer.

  1. 不要组合减速器。而是像通常那样将它们放在一个(嵌套的)函数对象中,但不要将它们组合起来。

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

  3. 使用injectReducer 注入新的reducer。

Here is an example:

下面是一个例子:

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

Full Disclosure: I am the creator of the module.

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

回答by Silviu-Marian

As of October 2017:

截至 2017 年 10 月:

  • Reedux

    implements what Dan suggested and nothing more, without touching your store, your project or your habits

  • Reedux

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

There are other libraries too but they might have too many dependencies, less examples, complicated usage, are incompatible with some middlewares or require you to rewrite your state management. Copied from Reedux's intro page:

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

回答by Code Ninja

We released a new library that helps modulating a Redux app and allows dynamically adding/removing Reducers and middlewares.

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

Please take a look at https://github.com/Microsoft/redux-dynamic-modules

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

Modules provide the following benefits:

模块提供以下好处:

  • Modules can be easily re-used across the application, or between multiple similar applications.

  • Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.

  • Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
  • 模块可以轻松地在整个应用程序中或在多个类似应用程序之间重用。

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

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

Features

特征

  • Group together reducers, middleware, and state into a single, re-usable module.
  • Add and remove modules from a Redux store at any time.
  • Use the included component to automatically add a module when a component is rendered
  • Extensions provide integration with popular libraries, including redux-saga and redux-observable
  • 将减速器、中间件和状态组合到一个可重用的模块中。
  • 随时从 Redux 存储中添加和删除模块。
  • 使用包含的组件在组件渲染时自动添加模块
  • 扩展提供了与流行库的集成,包括 redux-saga 和 redux-observable

Example Scenarios

示例场景

  • You don't want to load the code for all your reducers up front. Define a module for some reducers and use DynamicModuleLoader and a library like react-loadable to download and add your module at runtime.
  • You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
  • You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
  • 您不想预先加载所有减速器的代码。为一些减速器定义一个模块,并使用 DynamicModuleLoader 和像 react-loadable 这样的库在运行时下载和添加你的模块。
  • 您有一些常见的减速器/中间件,需要在应用程序的不同区域中重复使用。定义一个模块并将其轻松包含在这些区域中。
  • 您有一个包含多个共享相似状态的应用程序的单一存储库。创建一个包含一些模块的包并在您的应用程序中重复使用它们

回答by Maksym Oliinyk

Here is another examplewith code splitting and redux stores, pretty simple & elegant in my opinion. I think it may be quite useful for those who are looking for a working solution.

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

This storeis a bit simplified it doesn't force you to have a namespace (reducer.name) in your state object, of course there may be a collision with names but you can control this by creating a naming convention for your reducers and it should be fine.

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