代码分割

在大型 Web 应用程序中,通常需要将应用程序代码拆分为多个可以按需加载的 JS 包。 这种称为“代码分割”的策略通过减小初次加载时的 JS 的包的大小,来提高应用程序的性能。

要使用 Redux 进行代码拆分,我们希望能够将 reducer 动态添加到 store。 但是,Redux 实际上只有一个 root reducer 函数。 这个 root reducer 通常是在初始化应用程序时通过调用 combineReducers() 或类似函数生成的。 为了动态添加更多的 reducer,我们需要再次调用该函数来重新生成 root reducer。 下面,我们将讨论可以解决此问题的一些方法,并推荐提供此功能的两个库。

基本原则

使用 replaceReducer

Redux store 暴露出一个 replaceReducer 函数,该函数使用新的 root reducer 替代当前活动的 root reducer。调用该函数将替换内部 reducer 的引用,并 dispatch 一个 action 以初始化新加入的 reducer:

  1. const newRootReducer = combineReducers({
  2. existingSlice: existingSliceReducer,
  3. newSlice: newSliceReducer
  4. })
  5. store.replaceReducer(newRootReducer)

Reducer 注入

定义一个 injectReducer 函数

我们可能想从应用程序的任何地方调用 store.replaceReducer()。因此,它使我们可以很轻易的定义一个可重用的 injectReducer() 函数。该函数能够保持对所有现有 slice reducer 的引用,并可将新 reducer 附加到 store 实例。

  1. import { createStore } from 'redux'
  2. // 定义将始终存在于应用程序中的 Reducer
  3. const staticReducers = {
  4. users: usersReducer,
  5. posts: postsReducer
  6. }
  7. // Configure the store
  8. export default function configureStore(initialState) {
  9. const store = createStore(createReducer(), initialState)
  10. // 添加一个对象以跟踪已注册的异步 Reducer
  11. store.asyncReducers = {}
  12. //创建注入 reducer 函数
  13. // 此函数添加 async reducer,并创建一个新的组合 reducer
  14. store.injectReducer = (key, asyncReducer) => {
  15. store.asyncReducers[key] = asyncReducer
  16. store.replaceReducer(createReducer(this.asyncReducers))
  17. }
  18. // 返回修改后的 store
  19. return store
  20. }
  21. function createReducer(asyncReducers) {
  22. return combineReducers({
  23. ...staticReducers,
  24. ...asyncReducers
  25. })
  26. }

现在,只需要调用 store.injectReducer 函数即可向 store 添加新的 reducer。

使用 ‘Reducer Manager’

另一种方法是创建一个 ‘Reducer Manager’ 对象,它跟踪所有已注册的 Reducer 并暴露出 reduce() 函数。 请参考以下示例:

  1. export function createReducerManager(initialReducers) {
  2. // 创建一个将 key 映射到 reducer 的对象
  3. const reducers = { ...initialReducers }
  4. // 创建初始 CombinedReducer
  5. let combinedReducer = combineReducers(reducers)
  6. // 存储 key 的数组,用于删除 reducer 时删除 state 中对应的数据
  7. const keysToRemove = []
  8. return {
  9. getReducerMap: () => reducers,
  10. // 这个 root reducer 函数在该对象中暴露出
  11. // 并将传递给 store
  12. reduce: (state, action) => {
  13. // 如果已删除任何 reducer,请先清理 state 中对应的值
  14. if (keysToRemove.length > 0) {
  15. state = { ...state }
  16. for (let key of keysToRemove) {
  17. delete state[key]
  18. }
  19. keysToRemove = []
  20. }
  21. // Delegate to the combined reducer
  22. return combinedReducer(state, action)
  23. },
  24. // 添加具有指定 key 的新 reducer
  25. add: (key, reducer) => {
  26. if (!key || reducers[key]) {
  27. return
  28. }
  29. // 将 reducer 添加到 reducer 映射中
  30. reducers[key] = reducer
  31. // 生成新的 combined reducer
  32. combinedReducer = combineReducers(reducers)
  33. },
  34. // 使用指定的 key 删除 reducer
  35. remove: key => {
  36. if (!key || !reducers[key]) {
  37. return
  38. }
  39. // 从 reducer 映射中删除它
  40. delete reducers[key]
  41. // 将 key 添加到要清理的 key 列表中
  42. keysToRemove.push(key)
  43. // 生成新的 combined reducer
  44. combinedReducer = combineReducers(reducers)
  45. }
  46. }
  47. }
  48. const staticReducers = {
  49. users: usersReducer,
  50. posts: postsReducer
  51. }
  52. export function configureStore(initialState) {
  53. const reducerManager = createReducerManager(staticReducers)
  54. // 使用 root reducer 函数创建一个 store,该 root reducer 函数是 manager 暴露出的函数。
  55. const store = createStore(reducerManager.reduce, initialState)
  56. // 可选:将 reducer manager 添加到 store 上,以便于访问
  57. store.reducerManager = reducerManager
  58. }

要添加新的 reducer,现在可以调用 store.reducerManager.add("asyncState", asyncReducer)

要删除 reducer 现在可以调用 store.reducerManager.remove("asyncState")

库和框架

以下有一些优秀的库可以帮助您自动添加上述功能:

  • redux-dynamic-modules: 该库引入了“Redux Module”的概念,它是一组应该动态加载的 Redux 部件(Reducer,middleware)。它还暴露出一个 React 高阶组件用来在应用组件加载后加载 Module。 此外,它还与诸如 redux-thunkredux-saga 之类的库集成,以使这些库可以动态加载他们的部件(thunk,sagas)。
  • Redux 生态系统链接: Reducer - Reducer 动态注入