【问题标题】:How to dynamically load reducers for code splitting in a Redux application?如何在 Redux 应用程序中动态加载 reducer 以进行代码拆分?
【发布时间】:2016-01-03 06:08:09
【问题描述】:

我要迁移到 Redux。

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

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

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

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

这是一个好方法吗?

【问题讨论】:

    标签: javascript flux redux code-splitting


    【解决方案1】:

    更新:另见how Twitter does it

    这不是一个完整的答案,但应该可以帮助您入门。请注意,我不会丢弃旧的减速器——我只是将新的减速器添加到组合列表中。我认为没有理由丢弃旧的 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);
          })
        }
      };
    
      // ...
    }
    

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

    【讨论】:

    • 我很乐意看到将这种类型的功能添加到项目中。在处理代码拆分和大型应用程序时,动态添加减速器的能力是必须的。我有一些用户可能无法访问的整个子树,加载所有减速器是一种浪费。即使使用 redux-ignore 大型应用程序也可以真正堆叠 reducer。
    • 有时,“优化”一些无关紧要的东西会造成更大的浪费。
    • 希望上面的评论是有意义的......因为我的房间用完了。但基本上我看不到一种简单的方法可以将减速器组合到我们状态树上的单个分支中,当它们从不同的路由 /homepage 动态加载时,然后当用户转到他们的 @ 时,更多的分支会被加载987654327@ 一个如何做到这一点的例子,会很棒。否则我很难弄平我的状态树,或者我必须有非常具体的分支名称user-permissionsuser-personal
    • 如果我有初始状态,我该怎么做?
    • github.com/mxstbr/react-boilerplate 样板文件使用与此处提到的完全相同的技术来加载减速器。
    【解决方案2】:

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

    然后在配置存储和路由时,使用可以将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 的答案中,但我认为包含它可以避免混淆)
    【解决方案3】:

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

    这里是如何使用它:

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

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

    【讨论】:

      【解决方案4】:

      截至 2017 年 10 月:

      • Reedux

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

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

      【讨论】:

        【解决方案5】:

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

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

        模块提供以下好处:

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

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

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

        特点

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

        示例场景

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            猜你喜欢
            • 2021-04-16
            • 2018-10-03
            • 1970-01-01
            • 2017-02-05
            • 2018-09-21
            • 2017-10-24
            • 2019-08-19
            • 2017-01-28
            • 2019-01-22
            相关资源
            最近更新 更多