Connect: `mapStateToProps` を使用したデータ抽出
`connect` に渡される最初の引数である `mapStateToProps` は、接続されたコンポーネントに必要なストアからのデータの一部を選択するために使用されます。 多くの場合、短縮して `mapState` と呼ばれます。
- ストアの状態が変更されるたびに呼び出されます。
- ストア全体の状態を受け取り、このコンポーネントに必要なデータのオブジェクトを返す必要があります。
`mapStateToProps` の定義
`mapStateToProps` は関数として定義する必要があります。
function mapStateToProps(state, ownProps?)
`state` という名前の最初の引数と、オプションで `ownProps` という名前の2番目の引数を取り、接続されたコンポーネントに必要なデータを含むプレーンオブジェクトを返す必要があります。
この関数は `connect` の最初の引数として渡され、Redux ストアの状態が変更されるたびに呼び出されます。ストアを購読したくない場合は、`mapStateToProps` の代わりに `null` または `undefined` を `connect` に渡します。
**`mapStateToProps` 関数は、`function` キーワード(`function mapState(state) { }`)を使用して記述するか、アロー関数(`const mapState = (state) => { }`)として記述するかに関係なく、どちらの方法でも同じように動作します。**
引数
state
- `ownProps`(オプション)
`state`
`mapStateToProps` 関数の最初の引数は、Redux ストア全体の状態(`store.getState()` を呼び出したときに返される値と同じ)です。このため、最初の引数は伝統的に `state` と呼ばれています。(引数には任意の名前を付けることができますが、`store` と呼ぶのは正しくありません。それは「ストアインスタンス」ではなく「状態値」です。)
`mapStateToProps` 関数は、常に少なくとも `state` を渡して記述する必要があります。
// TodoList.js
function mapStateToProps(state) {
const { todos } = state
return { todoList: todos.allIds }
}
export default connect(mapStateToProps)(TodoList)
`ownProps`(オプション)
コンポーネントがストアからデータを取得するために独自の props からデータが必要な場合は、2番目の引数 `ownProps` を使用して関数を定義できます。この引数には、`connect` によって生成されたラッパーコンポーネントに渡されたすべての props が含まれます。
// Todo.js
function mapStateToProps(state, ownProps) {
const { visibilityFilter } = state
// ownProps would look like { "id" : 123 }
const { id } = ownProps
const todo = getTodoById(state, id)
// component receives additionally:
return { todo, visibilityFilter }
}
// Later, in your application, a parent component renders:
<ConnectedTodo id={123} />
// and your component receives props.id, props.todo, and props.visibilityFilter
`mapStateToProps` から返されるオブジェクトに `ownProps` の値を含める必要はありません。`connect` は、これらの異なる props ソースを自動的にマージして、最終的な props セットを作成します。
戻り値
`mapStateToProps` 関数は、コンポーネントに必要なデータを含むプレーンオブジェクトを返す必要があります。
- オブジェクト内の各フィールドは、実際のコンポーネントのプロパティになります。
- フィールドの値は、コンポーネントの再レンダリングが必要かどうかを判断するために使用されます。
例:
function mapStateToProps(state) {
return {
a: 42,
todos: state.todos,
filter: state.visibilityFilter,
}
}
// component will receive: props.a, props.todos, and props.filter
注: レンダリングのパフォーマンスをより詳細に制御する必要がある高度なシナリオでは、`mapStateToProps` は関数も返すことができます。この場合、その関数は特定のコンポーネントインスタンスの最終的な `mapStateToProps` として使用されます。これにより、インスタンスごとのメモ化を行うことができます。詳細については、ドキュメントの高度な使用方法: ファクトリー関数セクション、およびPR #279とそれに追加されたテストを参照してください。ほとんどのアプリケーションではこれは必要ありません。
使用上のガイドライン
`mapStateToProps` でストアからのデータを再整形する
`mapStateToProps` 関数は、単に `return state.someSlice` するだけでなく、さらに多くのことを行うことができます。**ストアデータをそのコンポーネントに必要なように「再整形する」責任があります。** これには、特定のプロパティ名として値を返すこと、ステートツリーの異なる部分からデータの一部を組み合わせること、ストアデータをさまざまな方法で変換することが含まれます。
セレクター関数を使用してデータの抽出と変換を行う
ステートツリーの特定の場所から値を抽出するプロセスをカプセル化するために、「セレクター」関数を使用することを強く推奨します。メモ化されたセレクター関数は、アプリケーションのパフォーマンス向上にも重要な役割を果たします(このページの次のセクションと、高度な使用方法: 派生データの計算ページで、セレクターを使用する理由と方法の詳細を確認してください)。
`mapStateToProps` 関数は高速である必要があります
ストアが変更されるたびに、接続されたすべてのコンポーネントのすべての `mapStateToProps` 関数が実行されます。そのため、`mapStateToProps` 関数はできるだけ高速に実行する必要があります。これは、遅い `mapStateToProps` 関数がアプリケーションの潜在的なボトルネックになる可能性があることも意味します。
「データの再整形」というアイデアの一部として、`mapStateToProps` 関数は多くの場合、さまざまな方法でデータを変換する必要があります(配列のフィルタリング、IDの配列を対応するオブジェクトへのマッピング、Immutable.js オブジェクトからのプレーンな JS 値の抽出など)。これらの変換は、変換を実行するコストと、その結果としてコンポーネントが再レンダリングされるかどうかという点で、コストがかかることがよくあります。パフォーマンスが懸念される場合は、入力値が変更された場合にのみこれらの変換が実行されるようにします。
`mapStateToProps` 関数はピュアで同期している必要があります
Redux リデューサーと同様に、`mapStateToProps` 関数は常に 100% ピュアで同期している必要があります。`state`(および `ownProps`)を引数としてのみ受け取り、それらの引数を変更せずに、コンポーネントに必要なデータを props として返す必要があります。データフェッチのために AJAX 呼び出しなどの非同期動作をトリガーするために使用することはできず、関数は `async` として宣言することはできません。
`mapStateToProps` とパフォーマンス
戻り値がコンポーネントの再レンダリングを決定する
React Redux は、`shouldComponentUpdate` メソッドを内部的に実装しており、コンポーネントに必要なデータが変更された場合にのみ、ラッパーコンポーネントが正確に再レンダリングされます。デフォルトでは、React Redux は、`mapStateToProps` から返されたオブジェクトの内容が、返されたオブジェクトの各フィールドに対して `===` 比較(「浅い等価性」チェック)を使用して異なるかどうかを判断します。フィールドのいずれかが変更された場合、コンポーネントは再レンダリングされ、更新された値を props として受け取ります。同じ参照の変更されたオブジェクトを返すことは一般的な間違いであり、予期したとおりにコンポーネントが再レンダリングされない原因となる可能性があることに注意してください。
ストアからデータを取り出すために `mapStateToProps` を使用して `connect` によってラップされたコンポーネントの動作をまとめると
(state) => stateProps | (state, ownProps) => stateProps | |
---|---|---|
`mapStateToProps` は次の場合に実行されます。 | ストアの `state` が変更された場合 | ストアの `state` が変更された場合 または `ownProps` のフィールドが異なっている場合 |
コンポーネントは次の場合に再レンダリングされます。 | `stateProps` のフィールドが異なっている場合 | `stateProps` のフィールドが異なっている場合 または `ownProps` のフィールドが異なっている場合 |
必要な場合にのみ新しいオブジェクト参照を返す
React Redux は、`mapStateToProps` の結果が変更されたかどうかを確認するために、浅い比較を行います。毎回新しいオブジェクトまたは配列の参照を誤って返すことは簡単であり、データが実際には同じであっても、コンポーネントが再レンダリングされる原因となります。
多くの一般的な操作によって、新しいオブジェクトまたは配列の参照が作成されます。
someArray.map()
またはsomeArray.filter()
を使用した新しい配列の作成array.concat
を使用した配列の結合array.slice
を使用した配列の一部選択Object.assign
を使用した値のコピー- スプレッド構文
{ ...oldState, ...newData }
を使用した値のコピー
これらの操作をメモ化されたセレクター関数に配置して、入力値が変更された場合にのみ実行されるようにします。これにより、入力値が変更されていない場合でも、mapStateToProps
は以前と同じ結果値を返し、connect
は再レンダリングをスキップできます。
データ変更時のみ高負荷処理を実行する
データ変換は多くの場合、負荷が高い(そして通常、新しいオブジェクト参照が作成される)可能性があります。mapStateToProps
関数をできるだけ高速にするには、関連データが変更された場合にのみ、これらの複雑な変換を再実行する必要があります。
これにはいくつかのアプローチがあります。
- アクションクリエーターまたはリデューサーで一部の変換を計算し、変換されたデータをストアに保持できます。
- コンポーネントの
render()
メソッドで変換を実行することもできます。 mapStateToProps
関数で変換を実行する必要がある場合は、メモ化されたセレクター関数を使用して、入力値が変更された場合にのみ変換が実行されるようにすることをお勧めします。
Immutable.jsのパフォーマンスに関する懸念事項
Immutable.jsの作者であるLee Byronは、Twitterでパフォーマンスが懸念される場合はtoJS
を避けるよう明示的にアドバイスしています
#immutablejsのパフォーマンスに関するヒント:.toJS()、.toObject()、.toArray()はすべて、構造共有を無効にする低速な完全コピー操作なので、避けてください。
Immutable.jsには他にもいくつかのパフォーマンスに関する懸念事項があります。詳細については、このページの最後に記載されているリンクのリストを参照してください。
動作と注意点
ストアの状態が同じ場合、mapStateToProps
は実行されません
connect
によって生成されたラッパーコンポーネントはReduxストアを購読します。アクションがディスパッチされるたびに、store.getState()
を呼び出し、lastState === currentState
かどうかを確認します。2つの状態値が参照によって同一である場合、ストアの残りの状態も変更されていないと仮定するため、mapStateToProps
関数は再実行されません。
ReduxのcombineReducers
ユーティリティ関数はこれに対して最適化を試みます。スライスリデューサーのいずれも新しい値を返さなかった場合、combineReducers
は新しいオブジェクトではなく古い状態オブジェクトを返します。これは、リデューサーでの変更がルート状態オブジェクトの更新につながらない可能性があり、そのためUIが再レンダリングされないことを意味します。
宣言された引数の数が動作に影響する
(state)
のみの場合、ルートストアの状態オブジェクトが異なるたびに関数が実行されます。(state, ownProps)
の場合、ストアの状態が異なる場合と、ラッパーpropsが変更された場合の両方に実行されます。
これは、実際使用する必要がある場合を除き、ownProps
引数を追加すべきではないことを意味します。そうでないと、mapStateToProps
関数は必要以上に頻繁に実行されます。
この動作にはいくつかのエッジケースがあります。必須引数の数が、mapStateToProps
がownProps
を受け取るかどうかを決定します。
関数の正式な定義に1つの必須パラメーターが含まれる場合、mapStateToProps
はownProps
を受け取りません。
function mapStateToProps(state) {
console.log(state) // state
console.log(arguments[1]) // undefined
}
const mapStateToProps = (state, ownProps = {}) => {
console.log(state) // state
console.log(ownProps) // {}
}
関数の正式な定義に0個または2個の必須パラメーターが含まれる場合、ownProps
を受け取ります。
function mapStateToProps(state, ownProps) {
console.log(state) // state
console.log(ownProps) // ownProps
}
function mapStateToProps() {
console.log(arguments[0]) // state
console.log(arguments[1]) // ownProps
}
function mapStateToProps(...args) {
console.log(args[0]) // state
console.log(args[1]) // ownProps
}
リンクと参照
チュートリアル
パフォーマンス
- パフォーマンスのために
toJS
、toArray
、toObject
を避けることを提案するLee Byronのツイート - Reselectを使用したReactとReduxのパフォーマンス向上
- 不変データのパフォーマンスに関するリンク
Q&A