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

n53p2ov0  于 2022-11-12  发布在  其他
关注(0)|答案(7)|浏览(168)

我打算迁移到Redux。
我的应用程序由很多部分(页面,组件)组成,所以我想创建很多reducer。Redux示例显示我应该使用combineReducers()来生成一个reducer。
另外,据我所知,Redux应用程序应该有一个存储,它是在应用程序启动后创建的。当创建存储时,我应该传递我的组合reducer。如果应用程序不是太大,这是有意义的。
但是如果我构建了多个JavaScript包呢?例如,应用程序的每个页面都有自己的包。我认为在这种情况下,一个组合的reducer是不好的。我看了Redux的源代码,我发现了replaceReducer()函数。这似乎是我想要的。
我可以为我的应用程序的每个部分创建组合的reducer,并在应用程序的各个部分之间移动时使用replaceReducer()
这是一个好办法吗?

8qgya5xd

8qgya5xd1#

更新:另请参阅how Twitter does it

这不是一个完整的答案,但应该可以帮助你开始。请注意,我不是丢弃旧的reducer-我只是在组合列表中添加新的reducer。我认为没有理由丢弃旧的reducer--即使在最大的应用程序中,你也不太可能拥有数千个动态模块,这正是你 * 可能 * 想要在应用程序中断开一些reducer的地方。

还原器. 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);
      })
    }
  };

  // ...
}

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

ia2d9nvy

ia2d9nvy2#

这是我如何在当前的应用程序中实现它的(基于GitHub问题中Dan的代码!)

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

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

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

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

它还包括为您的所有减速器启用热重装所需的配置。

abithluo

abithluo3#

现在有一个模块可以向redux存储中添加注入的reducer,这个模块叫做Redux Injector
以下是如何使用它:
1.不要合并reducer,而是像平常一样把它们放在一个(嵌套的)函数对象中,但不要组合它们。
1.使用redux注入器的createInjectStore,而不是redux的createStore。
1.用注射异径管注射新的异径管。
下面是一个示例:

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

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

v9tzhpje

v9tzhpje4#

截至2017年10月:

实现Dan的建议,仅此而已,不涉及您的商店、项目或习惯
也有其他的库,但是它们可能有太多的依赖项,较少的例子,复杂的用法,与一些中间件不兼容,或者需要你重写你的状态管理。

m1m5dgzv

m1m5dgzv5#

我们发布了一个新的库,它可以帮助调整Redux应用程序,并允许动态添加/删除Reducer和中间件。
请看一下https://github.com/Microsoft/redux-dynamic-modules

模块提供以下优点:

  • 模块可以在整个应用程序中或在多个类似的应用程序之间轻松地重用。
  • 组件声明它们所需的模块,redux-dynamic-modules确保为组件加载模块。
  • 模块可以动态地添加到存储中或从存储中删除,例如,当组件安装或用户执行操作时
    功能
  • 将reducer、中间件和状态组合到单个可重用模块中。
  • 可以随时从Redux存储中添加和删除模块。
  • 使用包含的组件在呈现组件时自动添加模块
  • 扩展提供了与流行库的集成,包括redux-saga和redux-observable
    示例方案
  • 您不想预先加载所有reducer的代码。请为某些reducer定义一个模块,并使用DynamicModuleLoader和react-loadable之类的库在运行时下载并添加模块。
  • 您有一些通用的reducer/中间件需要在应用程序的不同区域中重用。定义一个模块并轻松地将其包含在这些区域中。
  • 您有一个包含多个共享相似状态的应用程序的单一存储库。请创建一个包含一些模块的包,并在应用程序中重用它们
zf9nrax1

zf9nrax16#

这是另一个关于代码拆分和redux存储的例子,在我看来非常简单和优雅。我认为它可能对那些正在寻找工作解决方案的人非常有用。
这个存储器有点简化了,它不强制你在你的状态对象中有一个命名空间(reducer.name),当然可能会有名称冲突,但是你可以通过为你的reducer创建一个命名约定来控制这一点,这应该是好的。

cig3rfwq

cig3rfwq7#

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

减速器文件

将始终存在于应用程序中的staticReducer

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

现在,假设我们有一个组件,我们希望动态加载,并且该组件可能有自己的切片(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>

安装此组件后,我们将发现实体2注入到我们的存储中。

相关问题