コード分割
大規模なWebアプリケーションでは、アプリのコードを複数のJSバンドルに分割し、必要に応じてロードできるようにすることが望ましい場合があります。この戦略は「コード分割」と呼ばれ、フェッチする必要がある最初のJSペイロードのサイズを削減することで、アプリケーションのパフォーマンスを向上させるのに役立ちます。
Reduxでコード分割を行うには、リデューサーをストアに動的に追加できるようにする必要があります。ただし、Reduxには実際には単一のルートリデューサー関数しかありません。このルートリデューサーは通常、アプリケーションの初期化時にcombineReducers()
または同様の関数を呼び出すことによって生成されます。リデューサーを動的に追加するには、その関数を再度呼び出してルートリデューサーを再生成する必要があります。以下に、この問題を解決するためのいくつかのアプローチについて説明し、この機能を提供する2つのライブラリを参照します。
基本原則
replaceReducer
の使用
Reduxストアは、現在アクティブなルートリデューサー関数を新しいルートリデューサー関数に置き換えるreplaceReducer
関数を公開します。これを呼び出すと、内部のリデューサー関数の参照がスワップされ、新しく追加されたスライスリデューサーが自身を初期化するのに役立つアクションがディスパッチされます。
const newRootReducer = combineReducers({
existingSlice: existingSliceReducer,
newSlice: newSliceReducer
})
store.replaceReducer(newRootReducer)
リデューサーインジェクションのアプローチ
injectReducer
関数の定義
アプリケーション内のどこからでもstore.replaceReducer()
を呼び出したいと思うでしょう。そのため、既存のスライスリデューサーへの参照をすべて保持し、それをストアインスタンスにアタッチする再利用可能なinjectReducer()
関数を定義すると便利です。
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
を呼び出すだけです。
「リデューサーマネージャー」の使用
もう1つのアプローチは、「リデューサーマネージャー」オブジェクトを作成することです。このオブジェクトは、登録されたすべてのリデューサーを追跡し、reduce()
関数を公開します。次の例を検討してください。
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
}
新しいリデューサーを追加するには、store.reducerManager.add("asyncState", asyncReducer)
を呼び出すことができます。
リデューサーを削除するには、store.reducerManager.remove("asyncState")
を呼び出すことができます。
ライブラリとフレームワーク
上記機能を自動的に追加するのに役立ついくつかの優れたライブラリがあります。