如何在 Redux 应用程序中动态加载用于代码拆分的简化程序?更新:另请参阅Twitter如何做到这一点。

2022-08-30 01:24:07

我要迁移到 Redux。

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

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

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

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

这是一个好方法吗?


答案 1

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

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

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

  // ...
}

可能有更简洁的方式来表达这一点 - 我只是在展示这个想法。


答案 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
}

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

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