【React Redux】mapStateToProps の公式ドキュメント訳
React-reduxの公式ドキュメントを久しぶりに見てみると、いろいろと更新されていたので、ちゃんと読みこむことをしています。
今回、connect を使って store とコンポーネントを接続する際に使用する mapStateToProps
についての下記ドキュメントを google 翻訳の力で訳したので、自分用にメモ。
概要
connect
の最初の引数として、 mapStateToProps
は connect されたコンポーネントが必要とする store のデータを一部だけを渡すために使用される。
略して mapState
と呼ばれる。
- store の state が変更するたびに呼ばれる
- store の state 全体を受け取り、コンポーネントが必要するデータを プレーンな object として返す必要がある
mapStateToProps の定義
関数として定義する。
function mapStateToProps(state, ownProps?) {...}
store に subscribe したくない場合は mapStateToProps の代わりに null
or undefined
を渡して connect する。(不要な再レンダリングを防げる)
mapStateToProps は、function mapStateToProps(state) {}
の関数宣言文でも、 const mapStateToProps = (state) => { }
の関数式でも問題ない。
引数
state
mapStateToProps の最初の引数は store の state 全体(store.getState()
の呼び出しで返される値と同じ)。
そのため、最初の引数は伝統的に単に state
と呼ばれる。引数には任意の名前が可能だが store を呼び出すことは正しくない(「store インスタンス」ではなく「state value」)。
mapStateToPropsは 常に state
が引数に渡されて使われる必要がある。
// TodoList.js function mapStateToProps(state) { const { todos } = state; return { todoList: todos.allIds }; } export default connect(mapStateToProps)(TodoList);
ownProps
(オプショナル)
コンポーネントが store から データを取得するために、自分の props からデータを必要とする場合、第2引数の ownProps
を使って mapStateToPropsを定義できる。
この ownProps には connect で生成されたラッパーコンポーネントのすべての props が含まれる。
// ConnectedTodo.js function mapStateToProps(state, ownProps) { // ... const { id, name } = ownProps; return { // ... }; } export default connect(mapStateToProps)(ConnectedTodo);
// App.js <ConnectedTodo id={123} name="taro" />
戻り値
mapStateToProps は、コンポーネントが必要とするデータを含む プレーンなobjectを返す 必要がある。
- オブジェクトの各フィールドはコンポーネントの props として渡される
- フィールドの値は、コンポーネントを再レンダリングする必要があるかどうかを判断するために使用される
- フィールドの値に変更がない場合、再レンダリングされない
function mapStateToProps(state) { return { a: 1, todos: state.todos }; } // TodoListコンポーネントの props: props.a, props.todos export default connect(mapStateToProps)(TodoList);
※ レンダリングのパフォーマンスをさらに制御する必要がある高度なシナリオでは、mapStateToProps は関数を返すこともできる。
その場合、その関数は特定のコンポーネントインスタンスの最終的な mapStateToProps として使用される。これにより、インスタンスごとのメモ化を行うことができる。
しかし、ほとんどのアプリケーションはこれを必要としない。
使用ガイドライン
mapStateToProps に store からのデータを再形成させる
mapStateProps は単に state を分割して返すだけではなく、そのコンポーネントの必要に応じて store データを「再形成」する責任がある。
これには、特定の props 名として値を返すこと、state ツリーの様々な部分からのデータを組み合わせること、およびに、様々な方法で store データを変換することが含まれている。
selectors 関数を使ってデータを抽出および変換する
state ツリーの特定の場所から値を抽出するプロセスをカプセル化するのに役立つ selector
関数の使用を強く勧める。
メモ化された selector 関数は、アプリケーションのパフォーマンスを向上させる上でも重要な役割を果たす。
(selector
を使用する理由と方法の詳細については、次のセクションおよび「Advanced Usage: Performance」ページを参照)
mapStateToProps 関数は高速でなければならない
store が変更されるたびに connect されているすべてのコンポーネントの mapStateToProps 関数がすべて実行されるため、mapStateToProps 関数はできるだけ高速に実行する必要がある。
「データの再形成」の方法の一部として、mapStateToProps 関数はさまざまな方法でデータを変換する必要がある(配列のフィルタリング、IDの配列の対応するオブジェクトへのマッピング、Immutable.jsオブジェクトからのプレーンJS値の抽出など)。
多くの場合、これらの変換は、変換を実行するためのコストと、結果としてコンポーネントが再レンダリングされるかどうかの両方の点で不利益になる可能性がある。
パフォーマンスが懸念される場合は、入力値が変更された場合にのみこれらの変換が実行されるようにする。
mapStateToProps 関数は純粋で同期的な関数であるべき
redux reducer と同様に、mapStateToProps 関数は常に100%純粋で同期的である必要がある。
シンプルに state(および ownProps)を引数として受け取り、コンポーネントが props として必要とするデータを返す必要がある。
データを取得するための AJAX 呼び出しなどの非同期動作をトリガーするために使用しない。 また、関数を async function として宣言しない。
mapStateToProps とパフォーマンス
戻り値は、コンポーネントが再レンダリングされるかどうかを決める
React Redux は、コンポーネントが必要とするデータが変更されたときにラッパーコンポーネントが正確に再レンダリングされるように shouldComponentUpdate
メソッドを内部的に実装する。
デフォルトでは、React Redux は、返されたオブジェクトの各フィールドで ===
比較(「浅い等価性」チェック)を使用して、 mapStateToProps から返されたオブジェクトの内容が異なるかどうかを判断する。
いずれかのフィールドが変更された場合、コンポーネントは再レンダリングされ、更新された値を props として受け取ることができる。
同じ参照の変更されたオブジェクトを返すことはよくある間違いで、予期したときにコンポーネントが再レンダリングされない可能性があることに注意すること。
mapStateToProps に接続して store からデータを抽出してラップされたコンポーネントの動作を要約すると以下のようになる。
(state) => stateProps |
(state, ownProps) => stateProps |
|
---|---|---|
mapStateToProps が実行される時 | store の state が変更された |
store の state が変更されたまたは ownProps のいずれかのフィールドが異なっている |
コンポーネントが再レンダリングされる時 | stateProps のいずれかのフィールドが異なっている |
stateProps のいずれかのフィールドが異なっているまたは ownProps のいずれかのフィールドが異なっている |
必要な場合にのみ新しいオブジェクト参照を返す
React Redux は浅い比較(===
)を行って、mapStateToProps の結果が変更されたかどうかを確認する。
間違って毎回新しいオブジェクトまたは配列参照を返すことは簡単だが、これにより、データが実際に同じであってもコンポーネントが再レンダリングされてしまう。
下記のような一般的な操作で、新しいオブジェクトまたは配列参照が作成される。
Array.prototype.map()
,Array.prototype.filter()
を使用して新しい配列を作るArray.prototype.concat()
を使って配列をマージするArray.prototype.slice()
を使って配列の一部を抽出するObject.assign
を使って値をコピーする- スプレッド演算子
{ ...oldState, ...newState }
を使って値をコピーする
これらの操作をメモされた selector
関数に入れて、入力値が変更された場合のみに実行するようにする。
これにより、入力値が変更されていない場合、mapStateToProps は以前と同じ結果値を返し、connect は再レンダリングをスキップできる。
データが変更されたときにのみ高負荷な操作を実行する
多くの場合、データの変換には負荷がかかる(通常、新しいオブジェクト参照が作成される)。
mapStateToProps 関数を可能な限り高速にするには、関連データが変更されたときにのみこれらの複雑な変換を再実行するようにする。
これにアプローチする方法はいくつかある。
- 一部の変換は
Action Creator
またはreducer
で計算でき、変換されたデータはstore
に保持できる - 変換は、コンポーネントの
render()
メソッドでも実行できる - mapStateToProps 関数で変換を行う必要がある場合は、メモ化された
selector
関数を使用して、入力値が変更されたときにのみ変換が実行されるようにする
Immutable.jsのパフォーマンスの懸念
Immutable.js の著者である Twitter の Lee Byron は、パフォーマンスが懸念される場合は toJS
を避けることを明示的に推奨している。
行動と落とし穴
store の state が同じ場合、mapStateToProps は実行されない
connect によって生成されたラッパーコンポーネントは、Redux store に subscribe する。
Action が dispatch されるたびに、 store.getState()
を呼び出し、lastState === currentState
かどうかを確認する。
2つの state の値が参照によって同一である場合、mapStateToProps 関数は再実行されない。これは、store state の残りの部分も変更されていないと想定しているため。
Redux の combineReducers
は、このために最適化を試みる。
分割された reducer
のいずれも新しい値を返さなかった場合、combineReducers は新しいオブジェクトではなく古い state オブジェクトを返す。
これは、reducer での変更によっては、root state オブジェクトが更新されない可能性があり、UIが再レンダリングされないことを意味する。
宣言された引数の数は動作に影響する
引数1つ (state)
を使用すると、store の state オブジェクトが異なる場合、常にmapStateToProps 関数が実行される。
引数2つ (state、ownProps)
を使用すると、store の state が異なるときはいつでも実行され、ラッパーコンポーネントのプロパティが変更されるたびに実行される。
つまり、実際に使用する必要がない限り、ownProps
引数を追加しないこと。
追加すると、mapStateToProps 関数が必要以上に実行される。
この動作の周辺にはいくつかのエッジケースがある。
必須引数の数は、mapStateToProps が ownProps を受け取るかどうかを決める。
- 関数の引数が1つの場合、mapStateToProps は ownProps を受け取らない。
function mapStateToProps(state) { console.log(state) // state console.log(arguments[1]) // undefined }
- 関数の引数がゼロまたは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 }
所感
- 一番気になったのは下記の
selector
関数の説明。メモ化された selector 関数は、アプリケーションのパフォーマンスを向上させる上でも重要な役割を果たす。
この「メモ化された selector 関数」というのがピンと来ていないので、別途きちんと理解したいと思います。