Actions
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
Redux FAQ: アクション
なぜtypeは文字列であるべきなのか?なぜアクションタイプを定数化するべきなのか?
状態と同様に、シリアライズ可能なアクションはタイムトラベルデバッグやアクションの記録・再生といったReduxの特徴的な機能を可能にします。type値にSymbolを使用したり、アクション自体にinstanceofチェックを使用するとこの機能が損なわれます。文字列はシリアライズ可能で自己記述的であるため、より適切な選択肢です。ただし、ミドルウェアで使用されることを意図したアクションでは、SymbolやPromise、その他の非シリアライズ可能な値を使用しても問題ありません。アクションはストアに到達してレデューサーに渡される時点でシリアライズ可能であればよいのです。
パフォーマンス上の理由からシリアライズ可能性を完全に強制できないため、Reduxは各アクションがプレーンオブジェクトでありtypeが文字列であることのみをチェックします。それ以外は開発者に委ねられますが、すべてをシリアライズ可能に保つことがデバッグや問題再現に役立つでしょう。
一般的に使用されるコード片のカプセル化と一元化はプログラミングの基本概念です。手動で各所にアクションオブジェクトを作成しtype値を手書きすることも可能ですが、再利用可能な定数を定義することでコードの保守性が向上します。定数を別ファイルに配置すれば、import文のタイポをチェックできるため、誤った文字列を使用する事故を防げます。
参考情報
ドキュメント
ディスカッション
レデューサーとアクションは常に1対1で対応するべきか?
いいえ。独立した小さなレデューサー関数を書き、それぞれが状態の特定のスライスを更新する責任を持つことを推奨します。このパターンを「レデューサーの合成」と呼びます。あるアクションは複数のレデューサーで処理されることもあれば、一部でしか処理されないことも、全く処理されないこともあります。これにより、1つのアクションが状態ツリーの異なる部分に影響を与える可能性がある場合でも、コンポーネントは実際のデータ変更から切り離されます。「ducks」ファイル構造のように密結合を選択するユーザーもいますが、デフォルトでは1対1対応はなく、1つのアクションを複数のレデューサーで処理したい場合はそのパラダイムから脱却すべきです。
参考情報
ドキュメント
ディスカッション
AJAX呼び出しなどの「副作用」をどう表現すべきですか?非同期処理に「アクションクリエーター」「サンク」「ミドルウェア」が必要な理由は?
これは長く複雑なトピックであり、コードの構成方法やアプローチについて多様な意見が存在します。
意味のあるWebアプリケーションは、通常AJAXリクエストの作成を含む非同期作業を伴う複雑なロジックを実行する必要があります。このようなコードはもはや純粋な入力の関数ではなく、外部との相互作用は「副作用」として知られています。
Reduxは関数型プログラミングに影響を受けており、標準では副作用を実行する場所がありません。特にリデューサー関数は常に(state, action) => newStateという純粋関数でなければなりません。ただし、Reduxのミドルウェアを使用すると、ディスパッチされたアクションをインターセプトし、副作用を含む追加の複雑な動作を追加できます。
一般的にReduxでは、副作用を持つコードはアクション作成プロセスの一部とすべきです。このロジックはUIコンポーネント内で実行することも可能ですが、同じロジックを複数の場所から呼び出せるように再利用可能な関数に抽出するのが合理的です。つまり、アクションクリエーター関数です。
最もシンプルで一般的な方法は、より複雑な非同期ロジックをアクションクリエーターに記述できるようにするRedux Thunkミドルウェアを追加することです。他にも広く使われている方法として、ジェネレーターを使用してより同期処理のように見えるコードを記述できるRedux Sagaがあり、Reduxアプリで「バックグラウンドスレッド」や「デーモン」のように機能します。別のアプローチであるRedux Loopは、リデューサーが状態変化に応じて副作用を宣言し、別途実行できるようにする逆転の発想です。これら以外にも、副作用管理の方法について独自の見解を持つ多数のコミュニティ開発ライブラリやアイデアが存在します。
参考情報
ドキュメント
記事
ディスカッション
どの非同期ミドルウェアを使うべきですか? thunk、saga、observableなどをどう選別しますか?
利用可能な非同期/副作用ミドルウェアは多数ありますが、最も一般的に使用されるのはredux-thunk、redux-saga、redux-observableです。これらは異なる特性とユースケースを持つ別個のツールです。
一般的な指針として:
-
Thunkは複雑な同期ロジック(特にReduxストア全体の状態へのアクセスが必要なコード)や基本的な非同期ロジック(AJAX呼び出しなど)に最適です。
async/awaitを活用すれば、より複雑なPromiseベースのロジックにも適応可能です。 -
Sagaは複雑な非同期ロジックや分離された「バックグラウンドスレッド」型の動作に最適で、特にディスパッチされたアクションの監視が必要な場合(thunkでは不可能)に適しています。ジェネレーター関数と
redux-sagaの「エフェクト」オペレーターの理解が必要です。 -
Observableはsagaと同じ問題を解決しますが、非同期動作の実装にRxJSに依存します。RxJS APIの習熟が必須です。
多くのReduxユーザーはまずthunkから始め、アプリがより複雑な非同期ロジックを真に必要とする場合にのみ、sagaやobservableなどの追加ライブラリを導入することを推奨します。
sagaとobservableは同じユースケースを持つため、通常どちらか一方を採用します。ただしthunkとsaga/observableを併用することは全く問題ありません。これらは異なる問題を解決するためです。
記事
ディスカッション
1つのアクションクリエーターから複数のアクションを連続でディスパッチすべきですか?
アクションの構造に関する厳密なルールはありません。Redux Thunkのような非同期ミドルウェアを使えば、次のようなシナリオが可能です:
- 関連する複数のアクションを連続でディスパッチする
- AJAXリクエストの進捗を表すアクションをディスパッチする
- 状態に基づいて条件付きでアクションをディスパッチする
- アクションをディスパッチ後、即時に更新された状態を確認する
一般的に、これらのアクションが「関連しているが独立したもの」か、それとも「1つのアクションとして表現すべきもの」かを検討してください。リデューサーの可読性とアクションログの可読性のバランスを取ることが重要です。例えば、新しい状態ツリー全体を含むアクションを使えばリデューサーは1行で済みますが、変更が「なぜ発生したか」の履歴が残らないためデバッグが困難になります。一方、粒度を保つためにループ内でアクションを発行する場合は、別の方法で処理される新しいアクションタイプの導入を検討すべきサインです。
パフォーマンスが懸念される場所では、同期的に複数回ディスパッチするのは避けてください。ディスパッチをバッチ処理するアドオンやアプローチがいくつか存在します。
参考情報
ドキュメント
記事
ディスカッション