本文へスキップ

Redux FAQ: React Redux

目次

React Redux

React-Reduxを使うべき理由

Redux自体はスタンドアロンライブラリであり、React、Angular、Vue、Ember、vanilla JSなど、あらゆるUIレイヤーやフレームワークで使用できます。ReduxとReactは一般的に一緒に使用されますが、互いに独立しています。

あらゆる種類のUIフレームワークでReduxを使用する場合は、通常、UIコードからストアに直接対話するのではなく、「UIバインディング」ライブラリを使用してReduxをUIフレームワークと連携させます。

React-ReduxはReact用の公式Redux UIバインディングライブラリです。ReduxとReactを一緒に使用している場合は、これらの2つのライブラリをバインドするためにReact-Reduxも使用する必要があります。

手動でストアの購読ロジックを記述することは可能ですが、そうすると非常に反復的になります。さらに、UIパフォーマンスの最適化には複雑なロジックが必要になります。

ストアへの購読、更新データの確認、再レンダリングのトリガーは、より汎用的で再利用可能なものにすることができます。React-ReduxのようなUIバインディングライブラリはストアの対話ロジックを処理するため、自分でそのコードを記述する必要はありません。

全体的に、React-Reduxは優れたReactアーキテクチャを促進し、複雑なパフォーマンス最適化を実装します。また、ReduxとReactの最新のAPI変更に合わせて最新の状態に保たれています。

さらなる情報

ドキュメント

コンポーネントが再レンダリングされない、または`mapStateToProps`が実行されないのはなぜですか?

意図せず状態を直接変更または修正することは、アクションがディスパッチされた後、コンポーネントが再レンダリングされない最も一般的な理由です。Reduxは、Reducerが「不変的に」状態を更新することを期待しています。これは、実際には常にデータのコピーを作成し、変更をコピーに適用することを意味します。Reducerから同じオブジェクトを返す場合、内容に変更を加えたとしても、Reduxは何も変更されていないと見なします。同様に、React Reduxは、`shouldComponentUpdate`で受信したpropsに対して浅い等価性参照チェックを行うことでパフォーマンスを向上させようとします。すべての参照が同じ場合、`shouldComponentUpdate`は`false`を返し、元のコンポーネントの実際の更新をスキップします。

ネストされた値を更新する場合は、状態ツリーの上位にあるものすべてについても新しいコピーを返す必要があることを覚えておくことが重要です。`state.a.b.c.d`があり、`d`を更新したい場合は、`c`、`b`、`a`、`state`の新しいコピーも返す必要があります。この状態ツリーの変更ダイアグラムは、ツリーの深い部分の変更がどのようにしてツリー全体に影響するかを示しています。

「不変的にデータを更新する」とは、Immerを使用する必要があるという意味ではありません(ただし、確かに選択肢の1つです)。プレーンなJSオブジェクトと配列に対して、いくつかの異なるアプローチを使用して不変の更新を行うことができます。

  • `Object.assign()`または`_.extend()`などの関数、および`slice()`や`concat()`などの配列関数を使用したオブジェクトのコピー
  • ES2015の配列スプレッド演算子、およびES2018の同様のオブジェクトスプレッド演算子
  • 不変の更新ロジックをより単純な関数にラップするユーティリティライブラリ

さらなる情報

ドキュメント

記事

ディスカッション

コンポーネントが頻繁に再レンダリングされるのはなぜですか?

React Reduxは、実際のコンポーネントが必要な場合にのみ再レンダリングされるように、いくつかの最適化を実装しています。その1つは、`connect`に渡される`mapStateToProps`と`mapDispatchToProps`引数によって生成された組み合わせられたpropsオブジェクトに対する浅い等価性チェックです。残念ながら、浅い等価性は、`mapStateToProps`が呼び出されるたびに新しい配列またはオブジェクトインスタンスが作成されるケースでは役に立ちません。一般的な例としては、IDの配列をマップして一致するオブジェクト参照を返すものがあります。

const mapStateToProps = state => {
return {
objects: state.objectIds.map(id => state.objects[id])
}
}

配列に毎回まったく同じオブジェクト参照が含まれている場合でも、配列自体は異なる参照であるため、浅い等価性チェックは失敗し、React Reduxはラップされたコンポーネントを再レンダリングします。

余分な再レンダリングは、Reducerを使用してオブジェクトの配列を状態に保存すること、Reselectを使用してマップされた配列をキャッシュすること、またはコンポーネントに`shouldComponentUpdate`を手動で実装し、`_.isEqual`などの関数を使用してより詳細なprops比較を行うことによって解決できます。カスタム`shouldComponentUpdate()`をレンダリング自体よりも高価にしないように注意してください!常にプロファイラを使用して、パフォーマンスに関する仮定を確認してください。

接続されていないコンポーネントの場合、渡されているpropsを確認することをお勧めします。よくある問題は、親コンポーネントがレンダリング関数内でコールバックを再バインドすることです(例:`<Child onClick={this.handleClick.bind(this)} />`)。これにより、親が再レンダリングされるたびに新しい関数参照が作成されます。一般的に、親コンポーネントのコンストラクタでコールバックを一度だけバインドするのが良い習慣です。

さらなる情報

ドキュメント

記事

ディスカッション

ライブラリ

どのように`mapStateToProps`を高速化できますか?

React Reduxは`mapStateToProps`関数が呼び出される回数を最小限に抑えるように機能しますが、`mapStateToProps`が迅速に実行され、実行する作業量も最小限に抑えるようにすることは依然として重要です。Reselectを使用してメモ化された「セレクタ」関数を作成することが一般的に推奨されるアプローチです。これらのセレクタは組み合わせたり合成したりでき、パイプラインの後続のセレクタは、その入力が変更された場合にのみ実行されます。つまり、フィルタリングやソートなどを行うセレクタを作成し、必要な場合にのみ実際の作業が行われるようにすることができます。

さらなる情報

ドキュメント

記事

ディスカッション

接続されたコンポーネントで`this.props.dispatch`が利用できないのはなぜですか?

`connect()`関数は、どちらもオプションの2つの主要な引数を取ります。最初の`mapStateToProps`は、ストアが変更されたときにストアからデータを取得し、これらの値をpropsとしてコンポーネントに渡すために提供する関数です。2番目の`mapDispatchToProps`は、ストアの`dispatch`関数を使用するために提供する関数であり、通常は、呼び出されるとすぐにアクションを自動的にディスパッチするアクションクリエイターの事前バインドバージョンを作成することによって行われます。

`connect()`を呼び出すときに独自の`mapDispatchToProps`関数を提供しない場合、React Reduxはデフォルトバージョンを提供します。これは、単に`dispatch`関数をpropとして返すことを意味します。つまり、独自の関数を提供する場合は、`dispatch`は自動的に提供されません。それでもpropとして利用できるようにするには、`mapDispatchToProps`の実装で明示的に返す必要があります。

さらなる情報

ドキュメント

ディスカッション

トップレベルコンポーネントのみを接続する必要がありますか?それともツリー内の複数のコンポーネントを接続できますか?

初期のReduxドキュメントでは、コンポーネントツリーの一番上にある少数の接続されたコンポーネントのみを持つように推奨していました。しかし、時間と経験から、このようなコンポーネントアーキテクチャは、一般的に少数のコンポーネントがすべてのdescendantのデータ要件についてあまりにも多くのことを知る必要があり、多くの混乱を招くpropsを渡すことを強制することが示されています。

現在の推奨されるベストプラクティスは、コンポーネントを「プレゼンテーション」コンポーネントまたは「コンテナ」コンポーネントとして分類し、意味のある場所に接続されたコンテナコンポーネントを抽出することです。

Reduxの例で「最上位に1つのコンテナコンポーネント」を強調したのは間違いでした。これを絶対的な原則として捉えないでください。プレゼンテーションコンポーネントは別々に保つように心がけてください。都合の良いときに接続することでコンテナコンポーネントを作成します。親コンポーネントで同じ種類の子供コンポーネントにデータを提供するためにコードを重複していると感じたら、コンテナを抽出する時です。一般的に、親コンポーネントが子コンポーネントの「個人」データやアクションについて知りすぎていると感じたら、コンテナを抽出する時です。

実際、ベンチマークの結果、接続されたコンポーネントが多い方が、少ない場合よりも一般的にパフォーマンスが向上することが示されています。

一般的に、コンポーネントのデータフローの分かりやすさと責任範囲のバランスを見つけるようにしてください。

詳細情報

ドキュメント

記事

ディスカッション

ReduxとReact Context APIを比較するには?

類似点

ReduxとReactのContext APIはどちらも「プロップドリリング」に対処します。つまり、どちらも複数のコンポーネント層を通してプロップを渡すことなくデータを渡すことができます。内部的に、ReduxはReact Context APIを使用してストアをコンポーネントツリーに渡します。

相違点

Reduxを使用すると、Redux Dev Tools Extensionの機能を利用できます。アプリが実行するすべてのアクションを自動的にログに記録し、タイムトラベルを可能にします。過去のアクションをクリックして、その時点に戻ることができます。Reduxはミドルウェアの概念もサポートしており、アクションディスパッチごとにカスタマイズされた関数呼び出しをバインドできます。自動イベントロガー、特定のアクションのインターセプトなどがその例です。

ReactのContext APIでは、互いに通信する一対のコンポーネントを扱います。これにより、関連のないデータ間の適切な分離が実現します。また、コンポーネントでデータを使用する方法についても柔軟性があります。つまり、親コンポーネントの状態を提供でき、ラップされたコンポーネントにコンテキストデータプロップとして渡すことができます。

ReduxとReactのContextがデータを扱う方法には重要な違いがあります。Reduxはアプリ全体のデータを巨大なステートフルオブジェクトに保持します。提供されたreducer関数を実行することでデータの変更を推論し、ディスパッチされた各アクションに対応する次の状態を返します。React Reduxはその後、コンポーネントのレンダリングを最適化し、必要なデータが変更された場合にのみ各コンポーネントが再レンダリングされるようにします。一方、Contextは状態を保持しません。データの導管にすぎません。データの変更を表すには、親コンポーネントの状態に依存する必要があります。

詳細情報