NgRxの利点と機能の詳細
公開: 2022-03-11チームリーダーが開発者に、特定の問題を解決するためのいくつかのメソッドを作成するのではなく、多くの定型コードを作成するように指示する場合、説得力のある議論が必要です。 ソフトウェアエンジニアは問題解決者です。 彼らは物事を自動化し、不必要な定型文を避けることを好みます。
NgRxには定型コードが付属していますが、開発用の強力なツールも提供します。 この記事は、コードの記述にもう少し時間を費やすと、努力する価値のあるメリットが得られることを示しています。
ほとんどの開発者は、DanAbramovがReduxライブラリをリリースしたときに状態管理を使い始めました。 状態管理を使い始めたのは、それがトレンドであったからであり、それが欠けていたからではありません。 状態管理に標準の「HelloWorld」プロジェクトを使用している開発者は、同じコードを何度も何度も書いていることにすぐに気付く可能性があり、複雑さが増して利益が得られません。
最終的に、一部の人々は欲求不満になり、州の管理を完全に放棄しました。
NgRxに関する私の最初の問題
この定型的な懸念はNgRxの大きな問題だったと思います。 最初は、その背後にある全体像を見ることができませんでした。 NgRxはライブラリであり、プログラミングパラダイムや考え方ではありません。 ただし、このライブラリの機能と使いやすさを完全に把握するには、知識をもう少し広げて、関数型プログラミングに焦点を当てる必要があります。 そのとき、ボイラープレートコードを記述して、それについて満足するかもしれません。 (私はそれを意味します。)私はかつてNgRxに懐疑的でした。 今、私はNgRxのファンです。
少し前に、状態管理を使い始めました。 上記の定型文を体験したので、ライブラリの使用をやめることにしました。 私はJavaScriptが大好きなので、現在使用されているすべての一般的なフレームワークについて、少なくとも基本的な知識を身に付けようとしています。 これが私がReactを使っている間に学んだことです。
Reactにはフックと呼ばれる機能があります。 Angularのコンポーネントと同様に、フックは引数を受け入れて値を返す単純な関数です。 フックには、副作用と呼ばれる状態が含まれている可能性があります。 したがって、たとえば、Angularの単純なボタンは次のようにReactに変換できます。
@Component({ selector: 'simple-button', template: ` <button>Hello {{ name }}</button> `, }) export class SimpleButtonComponent { @Input() name!: string; } export default function SimpleButton(props: { name: string }) { return <button>{props.name} </button>; }ご覧のとおり、これは単純な変換です。
- SimpleButtonComponent => SimpleButton
- @Input()name => props.name
- テンプレート=>戻り値
私たちのReact関数SimpleButtonには、関数型プログラミングの世界で1つの重要な特徴があります。それは純粋関数です。 あなたがこれを読んでいるなら、私はあなたがその用語を少なくとも一度聞いたことがあると思います。 NgRx.ioは、重要な概念で純粋関数を2回引用しています。
- 状態の変化は、現在の状態と最新のアクションを実行して新しい状態を計算する
reducersと呼ばれる純粋関数によって処理されます。 - セレクターは、状態の一部を選択、導出、および構成するために使用される純粋関数です。
Reactでは、開発者は可能な限り純粋な関数としてフックを使用することをお勧めします。 Angularはまた、開発者がSmart-DumbComponentパラダイムを使用して同じパターンを実装することを推奨しています。
その時、私はいくつかの重要な関数型プログラミングのスキルが不足していることに気づきました。 関数型プログラミングの重要な概念を学んだ後、私は「ああ! 瞬間」:私はNgRxの理解を深め、それが提供する利点をよりよく理解するためにNgRxをもっと使いたいと思っていました。
この記事では、私の学習経験と、NgRxおよび関数型プログラミングについて得た知識を共有します。 NgRxのAPIや、アクションの呼び出し方法やセレクターの使用方法については説明しません。 代わりに、NgRxが優れたライブラリであることを理解するようになった理由を共有します。これは、比較的新しいトレンドであるだけでなく、多くの利点を提供します。
関数型プログラミングから始めましょう。
関数型プログラミング
関数型プログラミングは、他のパラダイムとは大きく異なるパラダイムです。 これは非常に複雑なトピックであり、多くの定義とガイドラインがあります。 ただし、関数型プログラミングにはいくつかのコアコンセプトが含まれており、それらを知ることはNgRx(および一般的なJavaScript)を習得するための前提条件です。
これらのコアコンセプトは次のとおりです。
- 純粋関数
- 不変の状態
- 副作用
繰り返しますが、これは単なるパラダイムであり、それ以上のものではありません。 ダウンロードして関数型ソフトウェアを作成するために使用するライブラリfunctional.jsはありません。 これは、アプリケーションの作成についての考え方にすぎません。 最も重要なコアコンセプトである純粋関数から始めましょう。
純粋関数
関数が2つの単純な規則に従う場合、その関数は純粋関数と見なされます。
- 同じ引数を渡すと、常に同じ値が返されます
- 内部関数の実行に伴う観察可能な副作用の欠如(外部状態の変更、I / O操作の呼び出しなど)
したがって、純粋関数は、いくつかの引数を受け入れ(または引数をまったく受け取らない)、期待値を返す単なる透過関数です。 この関数を呼び出しても、ネットワークやグローバルユーザーの状態の変更などの副作用は発生しません。
3つの簡単な例を見てみましょう。
//Pure function function add(a,b){ return a + b; } //Impure function breaking rule 1 function random(){ return Math.random(); } //Impure function breaking rule 2 function sayHello(name){ console.log("Hello " + name); }- 最初の関数は、同じ引数を渡すときに常に同じ答えを返すため、純粋です。
- 2番目の関数は非決定的であり、呼び出されるたびに異なる回答を返すため、純粋ではありません。
- 3番目の関数は、副作用(
console.logの呼び出し)を使用するため、純粋ではありません。
関数が純粋かどうかを見分けるのは簡単です。 純粋関数が不純な関数よりも優れているのはなぜですか? 考えるのが簡単だからです。 あなたがいくつかのソースコードを読んでいて、あなたが純粋であると知っている関数呼び出しを見ていると想像してください。 関数名が正しい場合は、それを調べる必要はありません。 あなたはそれが何も変わらないことを知っています、それはあなたが期待するものを返します。 多くのビジネスロジックを備えた巨大なエンタープライズアプリケーションがある場合、それは非常に時間の節約になる可能性があるため、デバッグには非常に重要です。
また、テストも簡単です。 何かを挿入したり、その中にいくつかの関数をモックしたりする必要はありません。引数を渡して、結果が一致するかどうかをテストするだけです。 テストとロジックの間には強いつながりがあります。コンポーネントがテストしやすい場合、そのコンポーネントがどのように、そしてなぜ機能するのかを理解するのは簡単です。
純粋関数には、メモ化と呼ばれる非常に便利でパフォーマンスに優しい機能が付属しています。 同じ引数を呼び出すと同じ値が返されることがわかっている場合は、結果をキャッシュするだけで、再度呼び出す時間を無駄にすることはありません。 NgRxは間違いなくメモ化の上にあります。 それが高速である主な理由の1つです。
「副作用はどうですか? 彼らはどこへ行くのですか?」 彼のGOTOトークで、ラス・オルセンは、クライアントが純粋関数に対して私たちにお金を払うのではなく、副作用に対して私たちにお金を払うと冗談を言っています。 それは本当です:それがどこかに印刷されなければ、誰も電卓の純粋関数を気にしません。 副作用は、関数型プログラミングの世界でその役割を果たします。 すぐにわかります。
今のところ、複雑なアプリケーションアーキテクチャを維持するための次のステップ、次のコアコンセプトである不変状態に移りましょう。
不変の状態
不変状態の簡単な定義は次のとおりです。
- 状態の作成または削除のみが可能です。 更新できません。
簡単に言うと、ユーザーオブジェクトの年齢を更新するには…:
let user = { username:"admin", age:28 }…次のように書く必要があります。
// Not like this newUser.age = 30; // But like this let newUser = {...user, age:29 }すべての変更は、古いオブジェクトからプロパティをコピーした新しいオブジェクトです。 そのため、私たちはすでに不変の状態にあります。
文字列、ブール値、および数値はすべて不変の状態です。既存の値を追加または変更することはできません。 対照的に、日付は変更可能なオブジェクトです。常に同じ日付オブジェクトを操作します。
不変性はアプリケーション全体に適用されます。年齢を変更する関数内でユーザーオブジェクトを渡す場合、ユーザーオブジェクトは変更しないでください。更新された年齢で新しいユーザーオブジェクトを作成し、それを返す必要があります。
function updateAge(user, age) { return {...user, age: age) } let user = {username: 'admin', age: 29}; let newUser = updateAge(user, 32);なぜ私たちはこれに時間と注意を向けるべきなのでしょうか? 強調する価値のあるいくつかの利点があります。
バックエンドプログラミング言語の利点の1つは、並列処理です。 状態の変化が参照に依存せず、すべての更新が新しいオブジェクトである場合、プロセスをチャンクに分割し、同じメモリを共有せずに無数のスレッドで同じタスクを実行できます。 サーバー間でタスクを並列化することもできます。
AngularやReactなどのフレームワークの場合、並列処理はアプリケーションのパフォーマンスを向上させるためのより有益な方法の1つです。 たとえば、Angularは、入力バインディングを介して渡すすべてのオブジェクトのプロパティをチェックして、コンポーネントを再レンダリングする必要があるかどうかを識別する必要があります。 ただし、デフォルトの代わりにChangeDetectionStrategy.OnPushを設定すると、各プロパティではなく参照によってチェックされます。 大規模なアプリケーションでは、これにより確実に時間を節約できます。 状態を不変に更新すると、このパフォーマンスが無料で向上します。
すべてのプログラミング言語とフレームワークが共有する不変状態のその他の利点は、純粋関数の利点と同様です。考えてテストするのが簡単です。 変更が古い状態から生まれた新しい状態である場合、何に取り組んでいるかを正確に把握し、状態がどのようにどこで変化したかを正確に追跡できます。 更新履歴が失われることはなく、状態の変更を元に戻す/やり直すことができます(React DevToolsはその一例です)。
ただし、単一の状態が更新された場合、それらの変更の履歴はわかりません。 銀行口座の取引履歴のような不変の状態を考えてみてください。 それは実質的に必需品です。
不変性と純粋さを確認したので、残りのコアコンセプトである副作用に取り組みましょう。
副作用
副作用の定義を一般化することができます:
- コンピュータサイエンスでは、操作、関数、または式が、ローカル環境の外部でいくつかの状態変数値を変更する場合、副作用があると言われています。 つまり、操作の呼び出し元に値(主な効果)を返す以外に、観察可能な効果があります。
簡単に言えば、関数スコープ外の状態を変更するすべてのもの(すべてのI / O操作と、関数に直接接続されていない一部の作業)は、副作用と見なすことができます。 ただし、副作用は関数型プログラミングの哲学と矛盾するため、純粋関数内で副作用を使用することは避けなければなりません。 純粋関数内でI/O演算を使用すると、純粋関数ではなくなります。
それでも、副作用がないアプリケーションは無意味なので、どこかに副作用がある必要があります。 Angularでは、純粋関数を副作用から保護する必要があるだけでなく、コンポーネントやディレクティブでも使用しないようにする必要があります。
この手法の美しさをAngularフレームワーク内に実装する方法を調べてみましょう。
機能的な角度プログラミング
Angularについて最初に理解することの1つは、メンテナンスとテストを容易にするために、コンポーネントをできるだけ頻繁に小さなコンポーネントに分離する必要があることです。 これは、ビジネスロジックを分割する必要があるために必要です。 また、Angular開発者は、レンダリング目的でのみコンポーネントを残し、すべてのビジネスロジックをサービス内に移動することをお勧めします。
これらの概念を拡張するために、Angularユーザーは「Dumb-SmartComponent」パターンを語彙に追加しました。 このパターンでは、サービス呼び出しが小さなコンポーネント内に存在しないことが必要です。 ビジネスロジックはサービスに存在するため、これらのサービスメソッドを呼び出し、それらの応答を待ってから、状態を変更する必要があります。 そのため、コンポーネントにはいくつかの動作ロジックが含まれています。
これを回避するために、ビジネスロジックと動作ロジックを含む1つのスマートコンポーネント(ルートコンポーネント)を作成し、入力プロパティを介して状態を渡し、出力パラメーターをリッスンするアクションを呼び出すことができます。 このように、小さなコンポーネントは本当にレンダリング目的のためだけです。 もちろん、ルートコンポーネントにはいくつかのサービス呼び出しが含まれている必要があり、それらを削除することはできませんが、そのユーティリティはレンダリングではなくビジネスロジックのみに制限されます。
カウンターコンポーネントの例を見てみましょう。 カウンターは、値を増減する2つのボタンと、 displayFieldを表示する1つのcurrentValueを持つコンポーネントです。 したがって、4つのコンポーネントになります。
- CounterContainer
- 増加ボタン
- DecreaseButton
- 現在の価値
すべてのロジックはCounterContainer内にあるため、3つすべてが単なるレンダラーです。 3つのコードは次のとおりです。
@Component({ selector: 'decrease-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Decrease </button>`, }) export class DecreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); } @Component({ selector: 'current-value', template: `<button> {{ currentValue }} </button>`, }) export class CurrentValueComponent { @Input() currentValue!: string; } @Component({ selector: 'increase-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Increase </button>`, }) export class IncreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); }それらがどれほどシンプルで純粋か見てください。 それらには状態や副作用はなく、入力プロパティと放出イベントに依存するだけです。 それらをテストするのがいかに簡単か想像してみてください。 それが彼らの本当の姿であるため、私たちはそれらを純粋なコンポーネントと呼ぶことができます。 これらは入力パラメーターのみに依存し、副作用はなく、同じパラメーターを渡すことで常に同じ値(テンプレート文字列)を返します。
したがって、関数型プログラミングの純粋関数は、Angularの純粋コンポーネントに転送されます。 しかし、すべてのロジックはどこに行きますか? ロジックはまだありますが、少し異なる場所、つまりCounterComponentにあります。

@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { @Input() disabled!: boolean; currentValue = 0; get decreaseIsDisabled() { return this.currentValue === 0; } get increaseIsDisabled() { return this.currentValue === 100; } constructor() {} ngOnInit(): void {} decrease() { this.currentValue -= 1; } increase() { this.currentValue += 1; } } ご覧のとおり、動作ロジックはCounterContainerにありますが、レンダリング部分が純粋なコンポーネント用であるため、レンダリング部分がありません(テンプレート内でコンポーネントを宣言します)。
ここですべてのデータ操作と状態変更を処理するため、必要なだけサービスを注入できます。 言及する価値のあることの1つは、深くネストされたコンポーネントがある場合、ルートレベルのコンポーネントを1つだけ作成してはならないということです。 それをより小さなスマートコンポーネントに分割し、同じパターンを使用することができます。 最終的には、各コンポーネントの複雑さとネストされたレベルによって異なります。
そのパターンから、その1つ上のレイヤーであるNgRxライブラリ自体に簡単にジャンプできます。
NgRxライブラリ
Webアプリケーションは次の3つのコア部分に分割できます。
- ビジネスの論理
- アプリケーションの状態
- レンダリングロジック
ビジネスロジックは、ネットワーク、入力、出力、APIなど、アプリケーションで発生しているすべての動作です。
アプリケーションの状態は、アプリケーションの状態です。 現在許可されているユーザーとしてグローバルにすることも、現在のカウンターコンポーネント値としてローカルにすることもできます。
レンダリングロジックには、DOMを使用したデータの表示、要素の作成または削除などのレンダリングが含まれます。
Dumb-Smartパターンを使用することで、レンダリングロジックをビジネスロジックとアプリケーションの状態から切り離しましたが、どちらも概念的に異なるため、それらを分割することもできます。 アプリケーションの状態は、現在のアプリのスナップショットのようなものです。 ビジネスロジックは、アプリに常に存在する静的な機能のようなものです。 それらを分割する最も重要な理由は、ビジネスロジックは、ほとんどの場合、アプリケーションコードで可能な限り回避したい副作用であるということです。 これは、機能パラダイムを備えたNgRxライブラリが輝いているときです。
NgRxを使用すると、これらすべてのパーツを切り離すことができます。 3つの主要な部分があります:
- レデューサー
- 行動
- セレクター
関数型プログラミングと組み合わせると、3つすべてが組み合わされて、あらゆるサイズのアプリケーションを処理するための強力なツールが得られます。 それぞれを調べてみましょう。
レデューサー
レデューサーは純粋関数であり、単純な署名があります。 古い状態をパラメータとして受け取り、古い状態または新しい状態から派生した新しい状態を返します。 状態自体は単一のオブジェクトであり、アプリケーションのライフサイクルとともに機能します。 これは、HTMLタグ、単一のルートオブジェクトのようなものです。
状態オブジェクトを直接変更することはできません。レデューサーを使用して変更する必要があります。 これには多くの利点があります。
- 状態変更ロジックは1つの場所に存在し、状態がどこでどのように変化するかを知っています。
- レデューサー関数は純粋関数であり、テストと管理が簡単です。
- レデューサーは純粋関数であるため、メモ化でき、キャッシュして余分な計算を回避できます。
- 状態の変化は不変です。 同じインスタンスを更新することはありません。 代わりに、常に新しいものを返します。 これにより、「タイムトラベル」デバッグエクスペリエンスが可能になります。
これは、レデューサーの簡単な例です。
function usernameReducer(oldState, username) { return {...oldState, username} }非常に単純なダミーレデューサーですが、長くて複雑なすべてのレデューサーのスケルトンです。 それらはすべて同じ利点を共有します。 アプリケーションには何百ものレデューサーを含めることができ、必要な数だけ作成できます。
カウンターコンポーネントの場合、状態とレデューサーは次のようになります。
interface State{ decreaseDisabled:boolean; increaseDisabled:boolean; currentValue:number; } const MIN_VALUE=0; const MAX_VALUE =100; function decreaseReducer(oldState) { const newValue = oldState.currentValue -1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MIN_VALUE } function increaseReducer(oldState) { const newValue = oldState.currentValue + 1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MAX_VALUE }コンポーネントから状態を削除しました。 次に、状態を更新して適切なレデューサーを呼び出す方法が必要です。 そのとき、アクションが始まります。
行動
アクションは、レデューサーを呼び出して状態を更新するようにNgRxに通知する方法です。 それがなければ、NgRxを使用することは無意味になります。 アクションは、現在のレデューサーにアタッチする単純なオブジェクトです。 それを呼び出した後、適切なレデューサーが呼び出されるので、この例では、次のアクションを実行できます。
enum CounterActions { IncreaseValue = '[Counter Component] Increase Value', DecreaseValue = '[Counter Component] Decrease Value', } on(CounterActions.IncreaseValue,increaseReducer); on(CounterActions.DecreaseValue,decreaseReducer);私たちの行動はレデューサーに付随しています。 これで、コンテナコンポーネントをさらに変更し、必要に応じて適切なアクションを呼び出すことができます。
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }注:状態を削除しました。まもなく追加し直します。
これで、 CounterContainerには状態変更ロジックがありません。 何をディスパッチするかを知っているだけです。 次に、このデータをビューに表示する方法が必要です。 それがセレクターの効用です。
セレクター
セレクターも非常に単純な純粋関数ですが、レデューサーとは異なり、状態を更新しません。 名前が示すように、セレクターは単にそれを選択します。 この例では、3つの単純なセレクターを使用できます。
function selectCurrentValue(state) { return state.currentValue; } function selectDicreaseIsDisabled(state) { return state.decreaseDisabled; } function selectIncreaseIsDisabled(state) { return state.increaseDisabled; } これらのセレクターを使用して、スマートCounterContainerコンポーネント内の状態の各スライスを選択できます。
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="ecreaseIsDisabled$ | async" (decrease)="decrease()" > </decrease-button> <current-value [currentValue]="currentValue$ | async"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled$ | async" > </increase-button> `, }) export class CounterContainerComponent implements OnInit { decreaseIsDisabled$ = this.store.select(selectDicreaseIsDisabled); increaseIsDisabled$ = this.store.select(selectIncreaseIsDisabled); currentValue$ = this.store.select(selectCurrentValue); constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }これらの選択は、デフォルトでは非同期です(一般にObservablesと同様)。 これは、少なくともパターンの観点からは重要ではありません。 状態から何かを選択するだけなので、同期のものについても同じことが言えます。
一歩下がって全体像を見て、これまでに達成したことを確認しましょう。 カウンターアプリケーションがあります。これには、互いにほとんど分離されている3つの主要部分があります。 アプリケーションの状態がそれ自体をどのように管理しているか、またはレンダリングレイヤーがどのように状態をレンダリングするかは誰にもわかりません。
切り離されたパーツは、ブリッジ(アクション、セレクター)を使用して相互に接続します。 それらは、たとえばモバイルバージョンのように、State Applicationコード全体を取得して、別のプロジェクトに移動できる程度に分離されています。 実装する必要があるのはレンダリングだけです。 しかし、テストはどうですか?
私の謙虚な意見では、テストはNgRxの最良の部分です。 このサンプルプロジェクトのテストは、三目並べのプレイに似ています。 純粋関数と純粋コンポーネントしかないので、それらをテストするのは簡単です。 ここで、このプロジェクトが数百のコンポーネントで大きくなるかどうかを想像してみてください。 同じパターンに従うと、さらに多くのピースを追加することになります。 乱雑で読めないソースコードのブロブになることはありません。
ほぼ完了です。 カバーしなければならない重要なことは1つだけです。それは副作用です。 これまで何度も副作用について触れてきましたが、どこに保管するかを説明することはできませんでした。
これは、副作用がケーキのアイシングであり、このパターンを構築することで、アプリケーションコードからそれらを非常に簡単に削除できるためです。
副作用
カウンターアプリケーションにタイマーがあり、3秒ごとに値が自動的に1ずつ増加するとします。 これは単純な副作用であり、どこかに住んでいる必要があります。 これは、定義上、Ajaxリクエストと同じ副作用です。
副作用について考えると、ほとんどの場合、主に2つの理由があります。
- 州の環境の外で何かをする
- アプリケーションの状態を更新しています
たとえば、LocalStorage内に状態を保存することが最初のオプションであり、Ajax応答から状態を更新することが2番目のオプションです。 しかし、どちらも同じシグネチャを共有しています。それぞれの副作用には、いくつかの出発点が必要です。 アクションを開始するように促すには、少なくとも1回呼び出す必要があります。
前に概説したように、NgRxには誰かにコマンドを与えるための優れたツールがあります。 それは行動です。 アクションをディスパッチすることで、任意の副作用を呼び出すことができます。 擬似コードは次のようになります。
function startTimer(){ setInterval(()=>{ console.log("Hello application"); },3000) } on(CounterActions.StartTime,startTimer) ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);それはかなり些細なことです。 前に述べたように、副作用は何かを更新するかどうかのどちらかです。 副作用が何も更新しない場合は、何もする必要はありません。 そのままにしておきます。 しかし、状態を更新したい場合、どのようにそれを行いますか? コンポーネントが状態を更新しようとするのと同じ方法、つまり別のアクションを呼び出します。 したがって、副作用の内部でアクションを呼び出し、状態を更新します。
function startTimer(store) { setInterval(()=> { // We are dispatching another action dispatch(CounterActions.IncreaseValue) }, 3000) } on(CounterActions.StartTime, startTimer); ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);これで、完全に機能するアプリケーションができました。
NgRxエクスペリエンスの要約
NgRxの旅を終える前に、私が言及したいいくつかの重要なトピックがあります。
- 示されているコードは、この記事のために私が発明した単純な擬似コードです。 デモンストレーション目的にのみ適しています。 NgRxは、実際のソースが存在する場所です。
- 関数型プログラミングをNgRxライブラリに接続することについての私の理論を証明する公式のガイドラインはありません。 これは、高度なスキルを持つ人々によって作成された数十の記事とソースコードサンプルを読んだ後に形成された私の意見です。
- NgRxを使用した後は、この単純な例よりもはるかに複雑であることに間違いなく気付くでしょう。 私の目標は、実際よりもシンプルに見えるようにすることではなく、少し複雑で目的地までの道のりが長くなる可能性があるとしても、さらに努力する価値があることを示すことでした。
- NgRxの最悪の使用法は、アプリケーションのサイズや複雑さに関係なく、どこでも使用することです。 NgRxを使用すべきでない場合があります。 たとえば、フォームで。 NgRx内にフォームを実装することはほぼ不可能です。 フォームはDOM自体に接着されています。 彼らは別々に住むことはできません。 それらを切り離そうとすると、NgRxだけでなくWebテクノロジー全般を嫌うことに気付くでしょう。
- 同じ定型コードを使用すると、たとえ小さな例であっても、将来私たちに利益をもたらす可能性があるとしても、悪夢に変わる可能性があります。 その場合は、NgRxエコシステム(ComponentStore)の一部である別のすばらしいライブラリと統合するだけです。
