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

Pav*_*rin 174 javascript flux code-splitting redux

我要迁移到Redux.

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

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

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

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

这是一个好方法吗?

Dan*_*mov 230

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

这不是一个完整的答案,但应该可以帮助您开始.请注意,我不会扔掉旧的减速器 -我只是在组合列表中添加新的减速器.我认为没有理由丢弃旧的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
  });
}
Run Code Online (Sandbox Code Playgroud)

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));
}
Run Code Online (Sandbox Code Playgroud)

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

  // ...
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我很乐意看到这种类型的功能添加到项目中.在处理代码拆分和大型应用程序时,必须动态添加reducer.我有一些用户可能无法访问的整个子树,并且加载所有的reducer是一种浪费.即使使用redux-ignore,大型应用程序也可以真正堆叠减速器. (11认同)
  • https://github.com/mxstbr/react-boilerplate样板使用与此处提到的完全相同的技术来加载reducer. (3认同)
  • 有时,"优化"无关紧要的事情会带来更大的浪费. (2认同)

Jon*_*nan 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
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

然后在配置存储和路由时,使用可以为reducer注册表指定的函数:

var routes = createRoutes(reducerRegistry)
var store = createStore(reducerRegistry)
Run Code Online (Sandbox Code Playgroud)

这些功能看起来像这样:

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
}
Run Code Online (Sandbox Code Playgroud)

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

它还包括必要的配置,以便为所有减速器启用热重新加载.


小智 6

现在有一个模块可以将注入减速器添加到redux存储中.它被称为Redux Injector.https://github.com/randallknutson/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);
Run Code Online (Sandbox Code Playgroud)

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


Sil*_*ian 6

截至 2017 年 10 月:

  • 瑞杜克斯

    实施 Dan 建议的内容,仅此而已,而不会影响您的商店、您的项目或您的习惯

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