このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
Reducerロジックの再利用
アプリケーションが成長するにつれ、reducerロジックに共通パターンが現れてきます。異なるデータ型に対して同じような処理を行う複数のreducerロジックがあることに気づき、各データ型に対して同じ共通ロジックを再利用して重複を減らしたくなるでしょう。あるいは、ストア内で特定のデータ型の複数の「インスタンス」を扱いたい場合もあるでしょう。しかし、Reduxストアのグローバルな構造にはトレードオフがあります:アプリケーション全体の状態を追跡しやすい一方で、特定の状態を更新するアクションを「ターゲット」することが難しくなります。特にcombineReducersを使用している場合に顕著です。
例として、アプリケーション内でA、B、Cという名前の複数のカウンターを追跡したいとします。初期のcounter reducerを定義し、combineReducersを使用して状態を設定します:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const rootReducer = combineReducers({
counterA: counter,
counterB: counter,
counterC: counter
})
残念ながら、この設定には問題があります。combineReducersは各スライスreducerに同じアクションを渡すため、{type : 'INCREMENT'}をディスパッチすると、実際には_3つすべて_のカウンター値がインクリメントされ、1つだけが更新されるわけではありません。関心のあるカウンターだけが更新されることを保証するために、counterロジックをラップする方法が必要です。
高階Reducerによる動作のカスタマイズ
Reducerロジックの分割で定義されているように、_高階Reducer_はreducer関数を引数として受け取り、結果として新しいreducer関数を返す関数です。「reducerファクトリ」とも見なせます。combineReducersは高階Reducerの一例です。このパターンを使用して、特定のアクションにのみ反応する専用バージョンのreducer関数を作成できます。
reducerを特殊化する最も一般的な方法は2つあります:特定のプレフィックスやサフィックスを付けた新しいアクション定数を生成する方法、またはアクションオブジェクト内に追加情報を付与する方法です。以下に例を示します:
function createCounterWithNamedType(counterName = '') {
return function counter(state = 0, action) {
switch (action.type) {
case `INCREMENT_${counterName}`:
return state + 1
case `DECREMENT_${counterName}`:
return state - 1
default:
return state
}
}
}
function createCounterWithNameData(counterName = '') {
return function counter(state = 0, action) {
const { name } = action
if (name !== counterName) return state
switch (action.type) {
case `INCREMENT`:
return state + 1
case `DECREMENT`:
return state - 1
default:
return state
}
}
}
これで、いずれかの方法を使用して特殊化されたカウンターreducerを生成し、関心のある状態部分に影響を与えるアクションをディスパッチできるようになります:
const rootReducer = combineReducers({
counterA: createCounterWithNamedType('A'),
counterB: createCounterWithNamedType('B'),
counterC: createCounterWithNamedType('C')
})
store.dispatch({ type: 'INCREMENT_B' })
console.log(store.getState())
// {counterA : 0, counterB : 1, counterC : 0}
function incrementCounter(type = 'A') {
return {
type: `INCREMENT_${type}`
}
}
store.dispatch(incrementCounter('C'))
console.log(store.getState())
// {counterA : 0, counterB : 1, counterC : 1}
アプローチを少し変えて、指定されたreducer関数と名前(または識別子)の両方を受け入れる、より汎用的な高階Reducerを作成することもできます:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const { name } = action
const isInitializationCall = state === undefined
if (name !== reducerName && !isInitializationCall) return state
return reducerFunction(state, action)
}
}
const rootReducer = combineReducers({
counterA: createNamedWrapperReducer(counter, 'A'),
counterB: createNamedWrapperReducer(counter, 'B'),
counterC: createNamedWrapperReducer(counter, 'C')
})
さらに進んで、汎用的なフィルタリング高階Reducerを作成することさえ可能です:
function createFilteredReducer(reducerFunction, reducerPredicate) {
return (state, action) => {
const isInitializationCall = state === undefined;
const shouldRunWrappedReducer = reducerPredicate(action) || isInitializationCall;
return shouldRunWrappedReducer ? reducerFunction(state, action) : state;
}
}
const rootReducer = combineReducers({
// check for suffixed strings
counterA : createFilteredReducer(counter, action => action.type.endsWith('_A')),
// check for extra data in the action
counterB : createFilteredReducer(counter, action => action.name === 'B'),
// respond to all 'INCREMENT' actions, but never 'DECREMENT'
counterC : createFilteredReducer(counter, action => action.type === 'INCREMENT')
};
これらの基本的なパターンにより、UI内でスマート接続コンポーネントの複数インスタンスを持ったり、ページネーションやソートなどの汎用的な機能に共通ロジックを再利用したりすることが可能になります。
この方法でreducerを生成することに加えて、同じアプローチを使用してアクションクリエイターを生成することもでき、ヘルパー関数で両方を同時に生成できます。アクション/reducerユーティリティについては、Action/Reducer GeneratorsおよびReducersライブラリを参照してください。
コレクション/アイテム Reducerパターン
このパターンでは、複数の状態を持ち、アクションオブジェクト内の追加パラメータに基づいて各状態を更新する共通reducerを使用できます。
function counterReducer(state, action) {
switch(action.type) {
case "INCREMENT" : return state + 1;
case "DECREMENT" : return state - 1;
}
}
function countersArrayReducer(state, action) {
switch(action.type) {
case "INCREMENT":
case "DECREMENT":
return state.map( (counter, index) => {
if(index !== action.index) return counter;
return counterReducer(counter, action);
});
default:
return state;
}
}
function countersMapReducer(state, action) {
switch(action.type) {
case "INCREMENT":
case "DECREMENT":
return {
...state,
state[action.name] : counterReducer(state[action.name], action)
};
default:
return state;
}
}