我要迁移到Redux。
我的应用程序由很多部分(页面,组件)组成,所以我想创建许多减速器。Redux示例表明,我应该使用combineReducers()来生成一个减速器。
另外,据我所知,Redux应用程序应该有一个存储,它是在应用程序启动后创建的。当商店被创建时,我应该通过我的组合减速器。如果应用程序不是太大,这是有意义的。
但如果我构建了多个JavaScript包呢?例如,应用程序的每个页面都有自己的bundle。我认为在这种情况下,一个组合减速器是不好的。我查看了Redux的源代码,找到了replaceReducer()函数。这似乎就是我想要的。
我可以为我的应用程序的每个部分创建组合减速器,并在应用程序的各个部分之间移动时使用replaceReducer()。
这是一个好方法吗?
更新:看看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);
})
}
};
// ...
}
也许有更简洁的表达方式——我只是展示一下这个想法。
这就是我如何在当前应用程序中实现它(基于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
}
下面是用这种设置创建的一个基本的实时示例,以及它的源代码:
例子
源
它还涵盖了必要的配置,以启用热重新加载的所有减速器。
以下是我实现这一点所遵循的方法。
我们有一个存储文件,其中静态减速器将始终存在于减速器中,而动态减速器将在安装所需组件时添加。
减速机的文件
静态减速器将始终存在于应用程序中
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);
})
}
};
// ...
}
也许有更简洁的表达方式——我只是展示一下这个想法。
现在有一个模块将注入还原器添加到redux存储中。它被称为Redux Injector。
下面是如何使用它:
不要合并减速器。相反,把它们放在一个(嵌套的)函数对象中,就像你通常会做的那样,但不要组合它们。
使用redux-injector中的createInjectStore,而不是redux中的createStore。
用injectReducer注入新的减速器。
这里有一个例子:
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);
完全披露:我是这个模块的创建者。