Reduxの高次レデューサー
Redux状態のリセットでは、ルートレデューサーを作成することにより、レデューサーロジックを再利用して状態をリセットする方法を見てきました。 しかし、それがレデューサーロジックを再利用する唯一の方法ではなく、高次のレデューサーが登場して、再利用性とコードの重複を減らすのに役立ちます。
ルートレデューサーアプローチとは異なり、高次レデューサーを使用すると、それを必要とするレデューサーに特定の機能を適用できます。 それらは機能的合成を使用してそうするので、最初にその概念を学びましょう。
高階関数
高階関数は、結果として別の関数を返す関数です。 たとえば、次の2つの引数を持つ単純な関数があるとします。
const greet = (first, second) => console.log(first + second)
greet('Hi ', 'little kitty'); // Hi little kitty
2つのネストされた関数で引数を分離することにより、高階関数でそれを書き直すことができます。
const greet = first => second => console.log(first + second)
greet('Hi ')('little kitty'); // Hi little kitty
greet('Hi ')
関数の呼び出しに注意してください。この関数は、('little kitty')
として呼び出している関数を返します。 重要なのは、このアプローチにより、コードを再利用するための機能合成が可能になるということです。
なぜそうするのですか? greet
関数を使用して、コードで Hi ${someone}を数回言いたいとします。 最初の例として単純なプレーン関数を使用してこれを行う場合は、Hi
を数回繰り返す必要があります。
greet('Hi ', 'dolphin');
greet('Hi ', 'camel');
greet('Hi ', 'snake');
高階関数を使用しながら、sayHi
関数を簡単に作成できます。
const sayHi = greet('Hi ');
sayHi('dolphin 🐬');
sayHi('camel 🐫');
sayHi('snake 🐍');
あなたはすでに2つの利点を見ることができます:
- より一般的な関数に基づいて、よりセマンティックな
sayHi
関数を作成しています 'Hi '
を繰り返していません
もちろん、これは単に教育目的の単純な例ですが、greet
関数は、より重いロジックを実行するものである可能性があり、前述の利点がはるかに明白になります。
高次レデューサー
これで、高階関数のメカニズムを理解したので、疑問に思うかもしれません。それは、レデューサーとどのように関連しているのでしょうか。 そういえば、レデューサーは単に関数なので、このパターンをレデューサーに適用するときは、高階レデューサーと呼びます。
例を見てみましょう。 usersおよびarticlesレデューサーがあり、APIからのdata
プロパティがあるとします。 何かのようなもの:
const defaultState = {
data: []
};
const userReducer = (state = defaultState, action) => {
//...
}
ユーザーと記事の両方にページネーションが必要だとします。 次に、GO_NEXT_PAGE
、GO_PREV_PAGE
、UPDATE_TOTAL_PAGES
などのアクションが必要になります。 ページネーションが必要な各レデューサーですべてのロジックを複製する必要がある場合は、非常に面倒になります。
ここで、高次のレデューサーを作成できます。レデューサーを指定すると、追加機能を備えた装飾されたレデューサーが返されます。 それをwithPagination
と呼びましょう:
const withPagination = reducer => (state, action) => {
switch(action.type) {
case 'GO_NEXT_PAGE':
return { ...state, page: state.page + 1 }
// ...
default:
return reducer(state, action);
}
};
これはおなじみではありませんか? はい、入れ子関数はレデューサーです。 レデューサーには(state, action)
署名があることに注意してください。
ここで起こっていることは、引数として指定されたreducer
に基づいて、GO_NEXT_PAGE
ロジックを追加する新しいレデューサー関数とdefault
ステートメントを返すことです。スイッチの場合、呼び出しを元のレデューサーにプロキシするだけです。 そのため、これにより、必要な場合にのみいくつかの機能を追加します。
高次レデューサーの使用
たとえば、combineReducers
関数を呼び出してアプリのルートレデューサーを作成する場合、作成済みのwithPagination
高階レデューサーを必要なレデューサーに適用できます。
import { combineReducers } from 'redux';
import withPagination from './higher-order-reducers/withPagination'
import users from './reducers/users';
import articles from './reducers/articles';
import login from './reducers/login';
const rootReducer = combineReducers({
users: withPagination(users),
articles: withPagination(articles),
login, // we don't need pagination for the login reducer
// ...
});
users
およびarticles
レデューサーにのみページネーションロジックを適用していることに注意してください。 これが高次レデューサーの力です。必要に応じて適用できます。
高次レデューサーのパラメーター化
前の例にはまだ問題があります。GO_TO_NEXT_PAGE
アクションをトリガーすると、users
とarticles
の両方が同じアクション名をチェックしているため、そのアクションを処理します。 。
ご想像のとおり、これはレデューサーごとに異なるアクション名を指定することで解決できるため、USERS_GO_TO_NEXT_PAGE
アクションとARTICLES_GO_TO_NEXT_PAGE
アクションがあります。
これを実現するには、セクションパラメーターをwithPagination
高階レデューサーに渡します。
const withPagination = (section, reducer) => (state, action) => {
switch(action.type) {
case `${section}_GO_NEXT_PAGE`:
return { ...state, page: state.page + 1 }
// ..
}
};
// ...
const rootReducer = combineReducers({
users: withPagination('USERS', users),
articles: withPagination('ARTICLES', articles),
login
});
まとめ
高階レデューサーパターンと、それが関数合成を使用してコードの重複を解決し、再利用性を向上させる方法を見てきました。
ここで何か新しいことを学び、Reduxの機能的性質を考慮して、Reduxまたは一般的なコードに適用できる複数のパターンがどのようにあるかを理解できることを願っています。
涼しくしてください🦄