再選択によるパフォーマンスの高いReduxセレクター
Reduxでは、セレクターはストアから特定の状態を取得するロジックの一部です。 さらに、セレクターは特定の状態からデータを計算できるため、ストアは基本的な生データのみを保持できます。 セレクターは通常、ストアとコンテナーコンポーネント間のバインディングの一部として使用されます。 たとえば、Reactでは、 React Reduxのconnect
関数に渡されるmapStateToProps
関数は、コンテナーコンポーネントに必要な状態のスライスを選択するためにセレクターを使用する場所です。 。
セレクターは、Reselectのようなライブラリを使用せずに作成できますが、Reselectを使用すると、開発者の経験とパフォーマンスの点でいくつかの利点があります。
- Reselectの
createSelector
機能で作成したセレクターをメモします。 これは、関数が最後に呼び出されたときに渡された引数を記憶し、引数が同じであるかどうかを再計算しないことを意味する派手な言葉です。 キャッシングのように少し見ることができます。 - 再選択セレクターは、簡単に構成/チェーン化できます。 このように、各セレクターは小さいままで、1つのタスクに集中します。
インストール
npmまたはYarnを使用して、reselect
パッケージをプロジェクトに追加するだけです。
$ npm install reselect
# or, using Yarn:
$ yarn add reselect
これにより、レデューサーと同じファイルまたは独自の別のファイルでセレクターの作成を開始できます。 ここでは、たとえば、単純なtodoアプリのセレクターをいくつか作成します。ここでは、文字列*”milk”*を含むtodoアイテムと文字列*”bread”*を含むtodoアイテムを選択します。
// ...
import { createSelector } from 'reselect';
const todoSelector = state => state.todo.todo;
export const todosWithMilk = createSelector([todoSelector], todos => {
return todos.filter(todo => todo.title.toLowerCase().includes('milk'));
});
export const todosWithMilkAndBread = createSelector([todosWithMilk], todos => {
return todos.filter(todo => todo.title.toLowerCase().includes('bread'));
});
// ...
この例では、todoSelector
、todosWithMilk
、todosWithMilkAndBread
の3つのセレクターがあります。 最初のセレクターは入力セレクターと呼ばれます。 入力セレクターは非常に単純で、引数として状態を受け取り、その状態のスライスを返します。 入力セレクターは常に純粋関数である必要があります。
この例の2番目と3番目のセレクターtodosWithMilk
とtodosWithMilkAndBread
は、ReselectのcreateSelector
関数を使用して作成されたセレクターです。 createSelector
は、2つの引数を想定しています。1番目の引数として入力セレクターの配列、2番目の引数として関数(変換関数と呼ばれる)です。 変換関数は、入力セレクターから結果を引数として受け取り、目的の計算された状態を返す必要があります。
セレクターの構成もここで示され、todosWithMilkAndBread
はtodosWithMilk
セレクターの上に構成されています。 これにより、セレクターロジックをシンプルに保つことができます。
入力セレクターを引数のリストとしてcreateSelector
に渡すこともできることに注意してください。これらは、配列内にある必要はありません。 渡された最後の引数が変換関数である限り。
セレクターの使用
Todos
コンテナコンポーネントでtodosWithMilk
およびtodosWithMilkAndBread
セレクターを利用してみましょう。
import React from 'react';
import { connect } from 'react-redux';
import { deleteTodo } from '../actions';
import { todosWithMilk, todosWithMilkAndBread } from '../reducers';
function Todos({
withMilk,
withMilkAndBread,
error,
loading
}) {
return (
<div>
{/* ... */}
</div>
);
}
const mapStateToProps = state => {
return {
withMilk: todosWithMilk(state),
withMilkAndBread: todosWithMilkAndBread(state),
error: state.todo.error,
loading: state.todo.loading
};
};
const mapDispatchToProps = dispatch => {
return {
onDelete: id => {
dispatch(deleteTodo(id));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Todos);
ご覧のとおり、渡された状態オブジェクトを使用してセレクターを呼び出すのと同じくらい簡単です。 舞台裏では、セレクターは、前回実行したときに同じ引数値で呼び出されたかどうかを確認し、その場合は状態を再計算しません。
より現実的な例
前の例はかなり単純で、実際のアプリでは、代わりに複数の入力セレクターがあり、これらの入力セレクターの結果に基づいて状態の目的のスライスを計算することがほとんどです。 たとえば、より現実的なToDoアプリでは、入力フィールドを使用して、指定された検索語を含む表示されているToDoアイテムをフィルターで絞り込むことができます。 これは、次のように少し見えるセレクターで実現できます。
// ...
import { createSelector } from 'reselect';
const todoSelector = state => state.todo.todos;
const searchTermSelector = state => state.todo.searchTerm;
export const filteredTodos = createSelector(
[todoSelector, searchTermSelector],
(todos, searchTerm) => {
return todos.filter(todo => todo.title.match(new RegExp(searchTerm, 'i')));
}
);
// ...
これにより、filteredTodos
セレクターを使用して、状態にsearchTerm
が設定されていない場合はすべてのタスクを取得でき、それ以外の場合はフィルターされたリストを取得できます。
👷これにより、Reduxアプリの一部としてセレクターを使い始めて、パフォーマンスのメリットを享受できるようになることを願っています。 プロジェクトのreadmeを参照して、APIの詳細を確認したり、セレクターでコンポーネントの小道具にアクセスするなどのより高度なユースケースについて学習したりできます。