我要迁移到Redux。

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

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

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

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

这是一个好方法吗?


当前回答

以下是我实现这一点所遵循的方法。 我们有一个存储文件,其中静态减速器将始终存在于减速器中,而动态减速器将在安装所需组件时添加。

减速机的文件

静态减速器将始终存在于应用程序中

const staticReducers = combineReducers({
  entities1: entities1,
});

const createReducer = (asyncReducers) => {
  return combineReducers({
    staticReducers,
    ...asyncReducers,
  });
};

export default createReducer;

存储文件

在这里我们可以有我们的自定义中间件,记录器等,我们可以传递在中间件数组。并像下面这样使用它。

import { createStore, applyMiddleware, compose } from "redux";
import createReducer from "./reducers";
import api from "./middlewares/api";

const middlewares = [ api, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer]
const composedEnhancers = composeWithDevTools(compose(...enhancers))
const store = createStore(createReducer(), composedEnhancers)

export default function configureStore() {
  // Add a dictionary to keep track of the registered async reducers
  store.asyncReducers = {};

  // Create an inject reducer function
  // This function adds the async reducer, and creates a new combined 
  // reducer
  store.injectReducer = (key, asyncReducer) => {
    store.asyncReducers[key] = asyncReducer;
    store.replaceReducer(createReducer(store.asyncReducers));
  };

  // Return the modified store
  return store;
}

export function getStore() {
  return store;
}

现在假设我们有一个想要动态加载的组件,并且该组件可能有自己的slice(reducer),那么我们可以调用inject reducer来动态地将其添加到现有的reducer中。

 const Counter2 = React.lazy(() =>
    import("../counter2/counter2").then(async (module) => {
    const entities2 = await 
    import("../../../store/entities2").then((todosModule) => 
    todosModule.default);
    store.injectReducer("entities2", entities2);
      return module;
    })
  )


  <React.Suspense fallback={<div>loading...</div>}>
     <Counter2  />
  </React.Suspense>

安装这个组件后,我们会发现entities2注入到我们的存储中。

其他回答

更新:看看Twitter是怎么做的。

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

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

  // ...
}

也许有更简洁的表达方式——我只是展示一下这个想法。

以下是我实现这一点所遵循的方法。 我们有一个存储文件,其中静态减速器将始终存在于减速器中,而动态减速器将在安装所需组件时添加。

减速机的文件

静态减速器将始终存在于应用程序中

const staticReducers = combineReducers({
  entities1: entities1,
});

const createReducer = (asyncReducers) => {
  return combineReducers({
    staticReducers,
    ...asyncReducers,
  });
};

export default createReducer;

存储文件

在这里我们可以有我们的自定义中间件,记录器等,我们可以传递在中间件数组。并像下面这样使用它。

import { createStore, applyMiddleware, compose } from "redux";
import createReducer from "./reducers";
import api from "./middlewares/api";

const middlewares = [ api, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer]
const composedEnhancers = composeWithDevTools(compose(...enhancers))
const store = createStore(createReducer(), composedEnhancers)

export default function configureStore() {
  // Add a dictionary to keep track of the registered async reducers
  store.asyncReducers = {};

  // Create an inject reducer function
  // This function adds the async reducer, and creates a new combined 
  // reducer
  store.injectReducer = (key, asyncReducer) => {
    store.asyncReducers[key] = asyncReducer;
    store.replaceReducer(createReducer(store.asyncReducers));
  };

  // Return the modified store
  return store;
}

export function getStore() {
  return store;
}

现在假设我们有一个想要动态加载的组件,并且该组件可能有自己的slice(reducer),那么我们可以调用inject reducer来动态地将其添加到现有的reducer中。

 const Counter2 = React.lazy(() =>
    import("../counter2/counter2").then(async (module) => {
    const entities2 = await 
    import("../../../store/entities2").then((todosModule) => 
    todosModule.default);
    store.injectReducer("entities2", entities2);
      return module;
    })
  )


  <React.Suspense fallback={<div>loading...</div>}>
     <Counter2  />
  </React.Suspense>

安装这个组件后,我们会发现entities2注入到我们的存储中。

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

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

// 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
}

下面是用这种设置创建的一个基本的实时示例,以及它的源代码:

例子 源

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

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

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

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

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

模块提供以下好处:

模块可以很容易地在应用程序之间或多个类似的应用程序之间重用。 组件声明它们所需要的模块,redux-dynamic-modules确保为组件加载模块。 模块可以动态地从存储中添加/删除,例如当一个组件挂载或当用户执行一个操作时

特性

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

示例场景

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