開発者ドキュメント

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つの利点を見ることができます:

もちろん、これは単に教育目的の単純な例ですが、greet関数は、より重いロジックを実行するものである可能性があり、前述の利点がはるかに明白になります。

高次レデューサー

これで、高階関数のメカニズムを理解したので、疑問に思うかもしれません。それは、レデューサーとどのように関連しているのでしょうか。 そういえば、レデューサーは単に関数なので、このパターンをレデューサーに適用するときは、高階レデューサーと呼びます。

例を見てみましょう。 usersおよびarticlesレデューサーがあり、APIからのdataプロパティがあるとします。 何かのようなもの:

const defaultState = {
  data: []
};

const userReducer = (state = defaultState, action) => {
  //...
}

ユーザー記事の両方にページネーションが必要だとします。 次に、GO_NEXT_PAGEGO_PREV_PAGEUPDATE_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アクションをトリガーすると、usersarticlesの両方が同じアクション名をチェックしているため、そのアクションを処理します。 。

ご想像のとおり、これはレデューサーごとに異なるアクション名を指定することで解決できるため、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または一般的なコードに適用できる複数のパターンがどのようにあるかを理解できることを願っています。

涼しくしてください🦄

モバイルバージョンを終了