メインコンテンツにスキップ

Redux FAQ:パフォーマンス

目次

パフォーマンス

Reduxは、パフォーマンスとアーキテクチャの観点から見て、どの程度「スケール」しますか?

これに対する単一の決定的な答えはありませんが、ほとんどの場合、どちらの場合でも懸念する必要はありません。

Reduxによって実行される作業は、一般的に、ミドルウェアとリデューサーでのアクションの処理(イミュータブルな更新のためのオブジェクトの複製を含む)、アクションがディスパッチされた後のサブスクライバーへの通知、および状態の変更に基づくUIコンポーネントの更新のいくつかの領域に分類されます。これらのそれぞれが十分に複雑な状況でパフォーマンス上の懸念になる可能性はありますが、Reduxの実装方法に本質的に遅い、または非効率的なものはありません。実際、特にReact Reduxは、不要な再レンダリングを削減するために大幅に最適化されており、React-Redux v5では以前のバージョンよりも著しい改善が見られます。

Reduxは、他のライブラリと比較して、すぐに使える状態では効率的ではない可能性があります。Reactアプリケーションでレンダリングのパフォーマンスを最大化するには、状態を正規化された形状で保存し、少数のコンポーネントだけでなく多くの個々のコンポーネントをストアに接続し、接続されたリストコンポーネントは、接続された子リストアイテムにアイテムIDを渡す必要があります(リストアイテムがIDで独自のデータをルックアップできるようにします)。これにより、実行されるレンダリングの総量が最小限に抑えられます。メモ化されたセレクター関数の使用も、パフォーマンス上の重要な考慮事項です。

アーキテクチャに関しては、Reduxはさまざまなプロジェクトとチームの規模でうまく機能するという逸話的な証拠があります。Reduxは現在、数百の企業と数千の開発者によって使用されており、NPMからの月間インストール数は数十万件に上ります。ある開発者は、次のように報告しています。

規模については、約500のアクションタイプ、約400のリデューサーケース、約150のコンポーネント、5つのミドルウェア、約200のアクション、約2300のテストがあります。

さらなる情報

ドキュメント

記事

ディスカッション

各アクションに対して「すべてのリデューサー」を呼び出すのは遅くなりませんか?

Reduxストアには、実際には単一のリデューサー関数しかないことに注意することが重要です。ストアは、現在の状態とディスパッチされたアクションをその1つのリデューサー関数に渡し、リデューサーが適切に処理できるようにします。

明らかに、単一の関数ですべての可能なアクションを処理しようとすると、関数のサイズと可読性の点でスケールがうまく機能しないため、実際の作業をトップレベルのリデューサーによって呼び出すことができる別々の関数に分割することが理にかなっています。特に、一般的な推奨パターンは、特定のキーで状態の特定のスライスの更新を管理する責任を負う、別のサブリデューサー関数を持つことです。Reduxに付属しているcombineReducers()は、これを実現する多くの可能な方法の1つです。また、ストアの状態をできるだけフラットかつ正規化しておくことを強くお勧めします。ただし、最終的には、リデューサーロジックを好きなように整理することができます。

ただし、多くの異なるリデューサー関数が組み合わされており、深くネストされた状態であっても、リデューサーの速度が問題になる可能性は低いでしょう。JavaScriptエンジンは1秒あたりに非常に多数の関数呼び出しを実行でき、ほとんどのリデューサーはおそらくswitchステートメントを使用し、ほとんどのアクションに対してデフォルトで既存の状態を返しているだけです。

リデューサーのパフォーマンスを実際に懸念している場合は、redux-ignorereduxr-scoped-reducerなどのユーティリティを使用して、特定のリデューサーのみが特定のアクションをリッスンするようにすることができます。また、redux-log-slow-reducersを使用して、パフォーマンスベンチマークを実行することもできます。

さらなる情報

ディスカッション

リデューサーで状態をディープクローンする必要がありますか?状態をコピーするのは遅くなりませんか?

イミュータブルに状態を更新するということは、一般的に、ディープコピーではなく、シャローコピーを作成することを意味します。シャローコピーは、コピーする必要があるオブジェクトとフィールドが少なく、実質的にいくつかのポインタを移動することになるため、ディープコピーよりもはるかに高速です。

さらに、状態をディープクローンすると、すべてのフィールドに新しい参照が作成されます。React-Reduxのconnect関数は、データが変更されたかどうかを判断するために参照比較に依存しているため、これは、他のデータが意味のある変更されていない場合でも、UIコンポーネントが不必要に再レンダリングされることを意味します。

ただし、影響を受けるネストの各レベルに対して、コピーおよび更新されたオブジェクトを作成する必要があります。これは特に高価になるはずではありませんが、可能な限り状態を正規化して浅くしておく必要があるもう1つの良い理由です。

Reduxの一般的な誤解:状態をディープクローンする必要があります。現実:内部のものが変わらない場合は、その参照を同じに保ちます!

さらなる情報

ドキュメント

ディスカッション

ストアの更新イベント数を減らすにはどうすればよいですか?

Reduxは、正常にディスパッチされた各アクション(つまり、アクションがストアに到達し、リデューサーによって処理された)の後にサブスクライバーに通知します。場合によっては、サブスクライバーが呼び出される回数を減らすと便利な場合があります。特に、アクションクリエーターが複数の異なるアクションを連続してディスパッチする場合。

いくつかの方法でバッチ機能を追加するいくつかのアドオンがあります。たとえば、redux-batched-actions(複数のアクションを1つのアクションとしてディスパッチし、リデューサーでそれらを「展開」できるようにする高階リデューサー)、redux-batched-subscribe(複数のディスパッチに対してサブスクライバー呼び出しをデバウンスできるストアエンハンサー)、またはredux-batch(単一のサブスクライバー通知でアクションの配列をディスパッチするストアエンハンサー)などです。

特にReact-Reduxでは、React-Redux v7以降、新しいbatch公開APIが利用可能になり、Reactイベントハンドラーの外部でアクションをディスパッチするときにReactの再レンダリング数を最小限に抑えるのに役立ちます。Reactのunstable_batchedUpdate()APIをラップし、イベントループティックでのすべての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())
})
}
}

関連情報

ディスカッション

ライブラリ

「1つの状態ツリー」を持つことでメモリ問題が発生しますか?多くのアクションをディスパッチするとメモリを消費しますか?

まず、生のメモリ使用量に関して言えば、Reduxは他のJavaScriptライブラリと違いはありません。唯一の違いは、さまざまなオブジェクト参照が、Backboneのようにさまざまな独立したモデルインスタンスに保存されるのではなく、1つのツリーにネストされていることです。次に、典型的なReduxアプリは、Reduxがモデルやコレクションのインスタンスを作成するのではなく、プレーンなJavaScriptオブジェクトと配列の使用を推奨しているため、同等のBackboneアプリよりもメモリ使用量がいくらか少なくなる可能性があります。最後に、Reduxは一度に単一の状態ツリー参照のみを保持します。そのツリーで参照されなくなったオブジェクトは、通常どおりガベージコレクションされます。

Redux自体はアクションの履歴を保存しません。ただし、Redux DevToolsはアクションをリプレイできるように保存しますが、これらは一般的に開発中にのみ有効になり、本番環境では使用されません。

関連情報

ドキュメント

ディスカッション

リモートデータをキャッシュするとメモリ問題が発生しますか?

ブラウザで実行されるJavaScriptアプリケーションで使用できるメモリの量は有限です。キャッシュのサイズが利用可能なメモリの量に近づくと、データをキャッシュするとパフォーマンスの問題が発生します。これは、キャッシュされたデータが非常に大きい場合、またはセッションが非常に長時間実行されている場合に問題になる傾向があります。これらの問題の可能性を認識しておくことは良いことですが、この認識によって、妥当な量のデータを効率的にキャッシュすることを躊躇すべきではありません。

リモートデータを効率的にキャッシュするためのいくつかのアプローチを以下に示します

まず、ユーザーが必要とする量のデータのみをキャッシュします。アプリケーションにレコードのページ分割されたリストが表示される場合、必ずしもコレクション全体をキャッシュする必要はありません。代わりに、ユーザーに表示されるものをキャッシュし、ユーザーがより多くのデータをすぐに必要とするとき(またはすぐに必要とする場合)にそのキャッシュに追加します。

次に、可能な場合はレコードの省略形をキャッシュします。レコードには、ユーザーに関係のないデータが含まれている場合があります。アプリケーションがこのデータに依存していない場合は、キャッシュから省略できます。

3つ目に、レコードのコピーを1つだけキャッシュします。これは、レコードに他のレコードのコピーが含まれている場合に特に重要です。レコードごとに一意のコピーをキャッシュし、ネストされた各コピーを参照に置き換えます。これは正規化と呼ばれます。正規化は、効率的なメモリ消費など、いくつかの理由でリレーショナルデータを格納するための推奨されるアプローチです。

関連情報

ディスカッション