Skip to main content

代码分割

¥Code Splitting

在大型 Web 应用中,通常需要将应用代码拆分为多个可以按需加载的 JS 包。此策略称为 '代码分割',通过减少必须获取的初始 JS 有效负载的大小,有助于提高应用的性能。

¥In large web applications, it is often desirable to split up the app code into multiple JS bundles that can be loaded on-demand. This strategy, called 'code splitting', helps to increase performance of your application by reducing the size of the initial JS payload that must be fetched.

为了使用 Redux 进行代码分割,我们希望能够动态地将 reducer 添加到存储中。然而,Redux 实际上只有一个根 reducer 功能。该根 reducer 通常是在应用初始化时通过调用 combineReducers() 或类似函数来生成的。为了动态添加更多 reducer,我们需要再次调用该函数来重新生成根 reducer。下面,我们讨论解决此问题的一些方法,并参考提供此功能的两个库。

¥To code split with Redux, we want to be able to dynamically add reducers to the store. However, Redux really only has a single root reducer function. This root reducer is normally generated by calling combineReducers() or a similar function when the application is initialized. In order to dynamically add more reducers, we need to call that function again to re-generate the root reducer. Below, we discuss some approaches to solving this problem and reference two libraries that provide this functionality.

基本原则

¥Basic Principle

使用 replaceReducer

¥Using replaceReducer

Redux 存储公开了 replaceReducer 函数,该函数用新的根 reducer 函数替换当前活动的根 reducer 函数。调用它将交换内部 reducer 函数引用,并分派一个操作来帮助任何新添加的切片 reducer 初始化自身:

¥The Redux store exposes a replaceReducer function, which replaces the current active root reducer function with a new root reducer function. Calling it will swap the internal reducer function reference, and dispatch an action to help any newly-added slice reducers initialize themselves:

const newRootReducer = combineReducers({
existingSlice: existingSliceReducer,
newSlice: newSliceReducer
})

store.replaceReducer(newRootReducer)

Reducer 注入方法

¥Reducer Injection Approaches

定义 injectReducer 函数

¥Defining an injectReducer function

我们可能希望从应用中的任何位置调用 store.replaceReducer()。因此,定义一个可重用的 injectReducer() 函数来保留对所有现有切片缩减程序的引用并将其附加到存储实例会很有帮助。

¥We will likely want to call store.replaceReducer() from anywhere in the application. Because of that, it's helpful to define a reusable injectReducer() function that keeps references to all of the existing slice reducers, and attach that to the store instance.

import { createStore } from 'redux'

// Define the Reducers that will always be present in the application
const staticReducers = {
users: usersReducer,
posts: postsReducer
}

// Configure the store
export default function configureStore(initialState) {
const store = createStore(createReducer(), initialState)

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

function createReducer(asyncReducers) {
return combineReducers({
...staticReducers,
...asyncReducers
})
}

现在,只需调用 store.injectReducer 即可将新的 reducer 添加到存储中。

¥Now, one just needs to call store.injectReducer to add a new reducer to the store.

使用 'reducer 管理器'

¥Using a 'Reducer Manager'

另一种方法是创建一个 'reducer 管理器' 对象,该对象跟踪所有已注册的 reducer 并公开 reduce() 函数。考虑以下示例:

¥Another approach is to create a 'Reducer Manager' object, which keeps track of all the registered reducers and exposes a reduce() function. Consider the following example:

export function createReducerManager(initialReducers) {
// Create an object which maps keys to reducers
const reducers = { ...initialReducers }

// Create the initial combinedReducer
let combinedReducer = combineReducers(reducers)

// An array which is used to delete state keys when reducers are removed
let keysToRemove = []

return {
getReducerMap: () => reducers,

// The root reducer function exposed by this object
// This will be passed to the store
reduce: (state, action) => {
// If any reducers have been removed, clean up their state first
if (keysToRemove.length > 0) {
state = { ...state }
for (let key of keysToRemove) {
delete state[key]
}
keysToRemove = []
}

// Delegate to the combined reducer
return combinedReducer(state, action)
},

// Adds a new reducer with the specified key
add: (key, reducer) => {
if (!key || reducers[key]) {
return
}

// Add the reducer to the reducer mapping
reducers[key] = reducer

// Generate a new combined reducer
combinedReducer = combineReducers(reducers)
},

// Removes a reducer with the specified key
remove: key => {
if (!key || !reducers[key]) {
return
}

// Remove it from the reducer mapping
delete reducers[key]

// Add the key to the list of keys to clean up
keysToRemove.push(key)

// Generate a new combined reducer
combinedReducer = combineReducers(reducers)
}
}
}

const staticReducers = {
users: usersReducer,
posts: postsReducer
}

export function configureStore(initialState) {
const reducerManager = createReducerManager(staticReducers)

// Create a store with the root reducer function being the one exposed by the manager.
const store = createStore(reducerManager.reduce, initialState)

// Optional: Put the reducer manager on the store so it is easily accessible
store.reducerManager = reducerManager
}

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

¥To add a new reducer, one can now call store.reducerManager.add("asyncState", asyncReducer).

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

¥To remove a reducer, one can now call store.reducerManager.remove("asyncState")

库和框架

¥Libraries and Frameworks

有一些很好的库可以帮助你自动添加上述功能:

¥There are a few good libraries out there that can help you add the above functionality automatically: