パフォーマンス
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
Redux FAQ: パフォーマンス
Reduxの「スケール」はパフォーマンスとアーキテクチャの面でどの程度ですか?
これに対する唯一の決定的な答えはありませんが、ほとんどの場合どちらの面でも懸念する必要はないでしょう。
Reduxが行う処理は主に以下の領域に分類されます:ミドルウェアとリデューサーでのアクション処理(不変更新のためのオブジェクト複製を含む)、アクション発行後のサブスクライバー通知、状態変更に基づくUIコンポーネントの更新です。十分に複雑な状況ではそれぞれがパフォーマンス上の懸念となる可能性はありますが、Reduxの実装自体に本質的な遅さや非効率性はありません。実際、特にReact Reduxは不要な再レンダリングを削減するために高度に最適化されており、React-Redux v5は以前のバージョンよりも顕著な改善を示しています。
他のライブラリと比較すると、Reduxはすぐに使える状態では効率的でないかもしれません。Reactアプリケーションで最大のレンダリングパフォーマンスを得るには、状態を正規化された形式で保存し、少数ではなく多くの個別コンポーネントをストアに接続し、接続されたリストコンポーネントはアイテムIDを子リストアイテムに渡すべきです(これによりリストアイテムがIDで自身のデータを参照できるようになります)。これにより全体的なレンダリング量を最小化できます。メモ化されたセレクター関数の使用も重要なパフォーマンス考慮事項です。
アーキテクチャに関しては、経験則としてReduxは様々なプロジェクト規模やチームサイズで良好に機能します。Reduxは現在、何百もの企業と何千人もの開発者によって使用されており、NPMから月に数十万回インストールされています。ある開発者は次のように報告しています:
規模の例:当社では約500のアクションタイプ、約400のリデューサーケース、約150のコンポーネント、5つのミドルウェア、約200のアクション、約2300のテストがあります
参考情報
ドキュメント
記事
ディスカッション
すべてのreducerを各アクションで呼び出すと遅くなりませんか?
重要な点として、Reduxストアが実際に持つのは単一のreducer関数だけです。ストアは現在のstateとディスパッチされたアクションをその一つのreducer関数に渡し、reducerが適切に処理できるようにします。
明らかに、単一関数ですべての可能なアクションを処理しようとすると、関数サイズや可読性の観点でスケールしません。そのため、実際の作業をトップレベルのreducerが呼び出せる別々の関数に分割することは理にかなっています。特に一般的に推奨されるパターンは、特定のキーでstateの特定部分を管理する専用のサブreducer関数を持つことです。Reduxに組み込まれている combineReducers() はこれを実現する多くの方法の一つです。また、ストアstateを可能な限りフラットで正規化された状態に保つことが強く推奨されます。ただし最終的には、reducerロジックをどのように組織化するかは完全にあなた次第です。
しかし、多くの異なるreducer関数が組み合わさっていたり、stateが深くネストされていたりしても、reducerの速度が問題になる可能性は低いです。JavaScriptエンジンは1秒間に膨大な数の関数呼び出しを処理でき、ほとんどのreducerは大抵 switch ステートメントを使用しており、多くのアクションに対して既存のstateをそのまま返すだけです。
もしreducerのパフォーマンスが実際に気になる場合は、redux-ignore や reduxr-scoped-reducer のようなユーティリティを使用して、特定のreducerだけが特定のアクションをリッスンするようにできます。また redux-log-slow-reducers を使用してパフォーマンスベンチマークを取ることも可能です。
参考情報
ディスカッション
reducer内でstateをディープクローンする必要がありますか? コピー処理が遅くなるのでは?
イミュータブルなstate更新は通常、ディープコピーではなくシャローコピーを作成することを意味します。シャローコピーはディープコピーより遥かに高速です。なぜならコピーするオブジェクトやフィールドが少なく、実質的にはポインタの移動だけで済むからです。
さらに、stateをディープクローンすると全てのフィールドに新しい参照が作成されます。React-Reduxの connect 関数はデータ変更を検知するために参照比較に依存しているため、他のデータが実質的に変化していなくてもUIコンポーネントが不必要に再レンダリングされることになります。
ただし、影響を受ける各ネストレベルに対して、コピーして更新されたオブジェクトを作成する必要があります。これは特に高コストになるべきではありませんが、可能であれば状態を正規化して浅く保つべきもう一つの理由です。
Reduxに関する一般的な誤解:状態をディープクローンする必要がある。実際:内部で変更されないものは参照を同じまま保持せよ!
参考情報
ドキュメント
ディスカッション
ストア更新イベントの数を減らすには?
Reduxはディスパッチが成功するたび(アクションがストアに到達しレデューサーで処理された場合)にサブスクライバーへ通知します。アクションクリエーターが複数の異なるアクションを連続してディスパッチする場合など、サブスクライバーの呼び出し回数を削減することが有用な場面があります。
このような場合に対応するため、いくつかのアドオンが様々な方法でバッチ処理機能を提供しています:
- redux-batched-actions(複数アクションを1つとしてディスパッチし、レデューサー内で「展開」できる高階レデューサー)
- redux-batched-subscribe(複数ディスパッチ時のサブスクライバー呼び出しをデバウンスするストアエンハンサー)
- redux-batch(アクションの配列を単一のサブスクライバー通知で処理するストアエンハンサー)
React-Reduxに限定すると、v7以降ではReactイベントハンドラー外でのアクションディスパッチ時の再レンダリング回数を最小化する新しいbatch公開APIが利用可能です。これはReactのunstable_batchedUpdate() APIをラップし、1イベントループティック内のReact更新を単一のレンダーパスにバッチ処理します。Reactは既に独自のイベントハンドラーコールバックでこの仕組みを内部使用しています。このAPIは実際にはReactコアではなく、ReactDOMやReact Nativeといったレンダラーパッケージの一部です。
React-ReduxはReactDOMとReact Nativeの両環境で動作する必要があるため、ビルド時に正しいレンダラーからこのAPIをインポートする処理を組み込みました。またこの関数をbatch()という名前で再エクスポートしています。以下のようにReact外で複数アクションをディスパッチする場合でも単一の更新にまとめるために使用できます:
import { batch } from 'react-redux'
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}
参考情報
ディスカッション
-
React Redux #263: Huge performance issue when dispatching hundreds of actions
-
React-Redux #1177: Roadmap: v6, Context, Subscriptions, and Hooks
ライブラリ
「単一の状態ツリー」はメモリ問題を引き起こしますか? 多くのアクションをディスパッチするとメモリを消費しますか?
まず、生のメモリ使用量という点では、Reduxは他のJavaScriptライブラリと変わりません。唯一の違いは、さまざまなオブジェクト参照が1つのツリーにネストされていることであり、Backboneのように独立したモデルインスタンスとして保存されるわけではありません。次に、典型的なReduxアプリは同等のBackboneアプリよりもメモリ使用量が少ない傾向があります。なぜならReduxは、モデルやコレクションのインスタンスを作成する代わりに、プレーンなJavaScriptオブジェクトと配列の使用を推奨しているからです。最後に、Reduxは一度に単一の状態ツリー参照のみを保持します。ツリー内で参照されなくなったオブジェクトは通常通りガベージコレクションされます。
Redux自体はアクションの履歴を保存しません。ただしRedux DevToolsは再生可能にするためにアクションを保存しますが、これらは通常開発中のみ有効で、本番環境では使用されません。
参考情報
ドキュメント
ディスカッション
リモートデータのキャッシュはメモリ問題を引き起こしますか?
ブラウザで実行されるJavaScriptアプリケーションが利用できるメモリ量には限りがあります。キャッシュサイズが利用可能メモリ量に近づくと、パフォーマンス問題が発生します。これはキャッシュデータが非常に大きい場合や、セッションが異常に長期間実行される場合に問題になりがちです。これらの問題の可能性を認識することは重要ですが、適切な量のデータを効率的にキャッシュすることをためらう理由にはなりません。
リモートデータを効率的にキャッシュするためのアプローチをいくつか紹介します:
まず、ユーザーが必要とする分だけキャッシュします。アプリケーションがページネーションされたレコードリストを表示する場合、コレクション全体をキャッシュする必要は必ずしもありません。代わりにユーザーに表示される部分をキャッシュし、ユーザーが追加データをすぐに(または間もなく)必要とする場合にキャッシュを追加します。
次に、可能な場合はレコードの簡略化された形式をキャッシュします。レコードにはユーザーに関連しないデータが含まれることがあります。アプリケーションがこのデータに依存していない場合、キャッシュから除外できます。
第三に、レコードの単一コピーのみをキャッシュします。これはレコードが他のレコードのコピーを含む場合に特に重要です。各レコードのユニークなコピーをキャッシュし、ネストされた各コピーを参照で置き換えます。これは正規化と呼ばれます。正規化はリレーショナルデータを保存するための推奨アプローチであり、いくつかの理由(効率的なメモリ消費を含む)があります。
参考情報
ディスカッション