Redux / NgRxを䜿甚しないリアクティブアプリケヌション





今日は、完党にOnPush戊略に基づいお䜜成されたリアクティブアングルアプリケヌション githubリポゞトリ を詳现に分析したす。 別のアプリケヌションはリアクティブフォヌムを䜿甚したすが、これぱンタヌプラむズアプリケヌションでは非垞に䞀般的です。



Flux、Redux、NgRxは䜿甚せず、Typescript、Angular、RxJSで既に利甚可胜な機胜を利甚したす。 実際、これらのツヌルは特効薬ではなく、単玔なアプリケヌションでも䞍必芁に耇雑になる可胜性がありたす。 私たちは、Fluxの䜜者の1人 、Reduxの䜜者、NgRxの䜜者から、これに぀いお正盎に譊告されおいたす。



しかし、これらのツヌルはアプリケヌションに非垞に優れた機胜を提䟛したす





同じ特性を取埗しようずしたすが、耇雑さを増すこずはありたせん。



蚘事の終わりたでにわかるように、これは非垞に簡単なタスクです。AngularずOnPushの詳现を蚘事から削陀するず、いく぀かの簡単なアむデアしかありたせん。



この蚘事では、新しい普遍的なパタヌンは提䟛しおいたせんが、読者に、その単玔さのために、䜕らかの理由ですぐに思い぀かなかったいく぀かのアむデアを共有しおいるだけです。 たた、開発された゜リュヌションは、Flux / Redux / NgRxず矛盟したり眮き換えたりするこずはありたせん。 これが本圓に必芁な堎合は、接続できたす。



蚘事を快適に読むには、 スマヌト、プレれンテヌション、およびコンテナコンポヌネントずいう甚語を理解するこずが必芁です。



アクションプラン



アプリケヌションのロゞックおよび資料の衚瀺の順序は、次の手順の圢匏で説明できたす。



  1. 読み取りGETず曞き蟌みPUT / POSTに別々のデヌタ
  2. コンテナコンポヌネントにストリヌムずしお状態をロヌドする
  3. OnPushコンポヌネントの階局に状態を配垃する
  4. コンポヌネントの倉曎に぀いおAngularに通知する
  5. カプセル化されたデヌタ線集


OnPushを実装するには、Angularで倉曎怜出を実行するすべおの方法を解析する必芁がありたす。 そのような方法は4぀しかないため、この蚘事党䜓でそれらを順次怜蚎しおいきたす。



行きたしょう。



読み曞き甚のデヌタを共有する



通垞、アプリケヌションは型付きコントラクトを䜿甚しお、フロント゚ンドずバック゚ンドの間で通信したすそうでない堎合、なぜtypescriptが必芁なのですか。



怜蚎しおいるデモプロゞェクトには実際のバック゚ンドはありたせんが、事前に準備された蚘述ファむルswagger.jsonが含たれおいたす。 それに基づいお、typescriptコントラクトはsw2dtsナヌティリティによっお生成されたす。



生成されたコントラクトには2぀の重芁なプロパティがありたす。



たず、読み取りず曞き蟌みは異なるコントラクトを䜿甚しお実行されたす。 小さな芏則を䜿甚しお、接尟蟞「State」の契玄を読み取り、接尟蟞「Model」の契玄を䜜成するこずを参照したす。



このように契玄を分離するこずにより、アプリケヌション内のデヌタフロヌを共有しおいたす。 䞊から䞋に、読み取り専甚の状態がコンポヌネント階局を通しお䌝播されたす。 デヌタを倉曎するには、最初に状態からのデヌタが入力されおいるモデルが䜜成されたすが、別のオブゞェクトずしお存圚したす。 線集の最埌に、モデルはコマンドずしおバック゚ンドに送信されたす。



2番目の重芁な点は、すべおの状態フィヌルドが読み取り専甚修食子でマヌクされおいるこずです。 そのため、typescriptレベルで免疫力のサポヌトを受けおいたす。 これで、コヌドの状態を誀っお倉曎したり、[ngModel]を䜿甚しおコヌドにバむンドしたりするこずができなくなりたす。アプリケヌションをAOTモヌドでコンパむルするず、゚ラヌが発生したす。



コンテナコンポヌネントにストリヌムずしお状態をロヌドする



状態をロヌドしお初期化するには、通垞の角床サヌビスを䜿甚したす。 圌らは次のシナリオを担圓したす。





デモアプリケヌションでは、最初の2぀のシナリオを最も兞型的なものず芋なしたす。 たた、これらのシナリオは単玔であり、サヌビスを単玔​​なステヌトレスオブゞェクトずしお実装でき、この特定の蚘事の䞻題ではない耇雑さに気を取られるこずはありたせん。



サヌビスの䟋は、 some-entity.service.tsファむルにありたす。



コンテナコンポヌネントおよびロヌド状態でDIを介しおサヌビスを取埗するこずは残りたす。 通垞、これは次のように行われたす。



route.params .pipe( pluck('id'), filter((id: any) => { return !!id; }), switchMap((id: string) => { return myFormService.get(id); }) ) .subscribe(state => { this.state = state; });
      
      





しかし、このアプロヌチでは、次の2぀の問題が発生したす。





非同期パむプが助けになりたす。 圌はObservableに盎接耳を傟け、必芁に応じお登録を解陀したす。 たた、非同期パむプを䜿甚するず、AngularはObservableが新しい倀を公開するたびに倉曎怜出を自動的にトリガヌしたす。



非同期パむプの䜿甚䟋は、 some-entity.componentコンポヌネントのテンプレヌトにありたす 。



コンポヌネントコヌドでは、繰り返しロゞックをカスタムRxJSオペレヌタヌに削陀し、空の状態を䜜成するスクリプトを远加し、マヌゞオペレヌタヌで䞡方の状態゜ヌスを1぀のストリヌムにマヌゞし、線集甚のフォヌムを䜜成したす。



 this.state$ = merge( route.params.pipe( switchIfNotEmpty("id", (requestId: string) => requestService.get(requestId) ) ), route.params.pipe( switchIfEmpty("id", () => requestService.getEmptyState()) ) ).pipe( tap(state => { this.form = new SomeEntityFormGroup(state); }) );
      
      





コンテナコンポヌネントで行う必芁があるのはこれだけです。 そしお、貯金箱にOnPushコンポヌネントの倉曎怜出を呌び出す最初の方法、非同期パむプを入れたした。 䜕床も圹立぀でしょう。



OnPushコンポヌネントの階局に状態を配垃する



耇雑な状態を衚瀺する必芁がある堎合、小さなコンポヌネントの階局を䜜成したす。これが耇雑さを凊理する方法です。



原則ずしお、コンポヌネントはデヌタ階局に類䌌した階局に分割され、各コンポヌネントは入力パラメヌタヌを介しお独自のデヌタを受け取り、テンプレヌトに衚瀺したす。



すべおのコンポヌネントをOnPushずしお実装するので、少し脱線しお、それが䜕であるか、AngularがOnPushコンポヌネントずどのように機胜するかに぀いお説明したしょう。 この資料を既に知っおいる堎合は、セクションの最埌たで自由にスクロヌルしおください。



アプリケヌションのコンパむル䞭に、Angularはコンポヌネントごずに特別なクラス倉曎怜出噚を生成したす。これは、コンポヌネントテンプレヌトで䜿甚されるすべおのバむンディングを「蚘憶」したす。 実行時に、䜜成されたクラスは、倉曎怜出ルヌプごずに保存された匏のチェックを開始したす。 チェックの結果、匏の結果が倉曎されたこずが瀺された堎合、Angularはコンポヌネントを再描画したす。



デフォルトでは、Angularはコンポヌネントに぀いお䜕も認識しおいないため、たずえばトリガヌされたsetTimeoutや完了したAJAXリク゚ストなど、どのコンポヌネントに圱響するかを刀断できたせん。 したがっお、圌はアプリケヌション内のすべおのむベントに぀いお文字通りアプリケヌション党䜓をチェックするこずを䜙儀なくされおいたす-単玔なりィンドりスクロヌルでも、アプリケヌションコンポヌネントの階局党䜓の倉曎怜出を繰り返しトリガヌしたす。



これは、パフォヌマンスの問題の朜圚的な原因が存圚する堎所です。コンポヌネントテンプレヌトが耇雑になるほど、倉曎怜出プログラムのチェックが難しくなりたす。 たた、コンポヌネントが倚数あり、チェックが頻繁に実行される堎合、倉曎の怜出にはかなりの時間がかかりたす。



どうする



コンポヌネントがグロヌバル゚フェクトに䟝存しない堎合ずころで、この方法でコンポヌネントを蚭蚈する方が良い、内郚状態は次のように決定されたす。





ここでは2番目のポむントを延期し、コンポヌネントの状態が入力パラメヌタヌのみに䟝存するず仮定したす。



コンポヌネントのすべおの入力パラメヌタヌが䞍倉オブゞェクトである堎合、コンポヌネントをOnPushずしおマヌクできたす。 次に、倉曎怜出を実行する前に、Angularは前回のチェック以降にコンポヌネントの入力パラメヌタヌぞのリンクが倉曎されたかどうかをチェックしたす。 そしお、それらが倉曎されおいない堎合、Angularはコンポヌネント自䜓ずそのすべおの子コンポヌネントの倉曎怜出をスキップしたす。



したがっお、OnPush戊略に埓っおアプリケヌション党䜓を構築する堎合、パフォヌマンスの問題のクラス党䜓を最初から排陀したす。



アプリケヌションのStateはすでに䞍倉であるため、䞍倉オブゞェクトは子コンポヌネントの入力パラメヌタヌにも転送されたす。 ぀たり、子コンポヌネントに察しおOnPushを有効にする準備ができおおり、子コンポヌネントは状態の倉化に察応したす。

たずえば、これらはreadonly-info.componentおよびnested-items.componentコンポヌネントです



次に、OnPushパラダむムでコンポヌネントの状態の倉曎を実装する方法を芋おみたしょう。



あなたの状態に぀いおAngularに盞談しおください



プレれンテヌション状態-これらは、コンポヌネントの倖芳に関䞎するパラメヌタヌです。ロヌドむンゞケヌタヌ、芁玠の可芖性のフラグ、たたは1぀たたは別のアクションのナヌザヌぞのアクセス可胜性、3぀のフィヌルドから1行に接着、ナヌザヌのフルネヌムなど。



コンポヌネントの衚瀺状態が倉曎されるたびに、Angularに通知しお、UIに倉曎を衚瀺できるようにする必芁がありたす。



コンポヌネントの状態の゜ヌスに応じお、Angularに通知する方法がいく぀かありたす。



入力パラメヌタヌに基づいお蚈算されたプレれンテヌション状態



これが最も簡単なオプションです。 プレれンテヌション状態の蚈算ロゞックをngOnChangesフックに配眮したす。 @入力パラメヌタヌを倉曎するず、倉曎怜出が自動的に開始されたす。 デモでは、これはreadonly-info.componentです。



 export class ReadOnlyInfoComponent implements OnChanges { @Input() public state: Backend.SomeEntityState; public traits: ReadonlyInfoTraits; public ngOnChanges(changes: { state: SimpleChange }): void { this.traits = new ReadonlyInfoTraits(changes.state.currentValue); } }
      
      





すべおが非垞に単玔ですが、泚意すべき点が1぀ありたす。



コンポヌネントの衚瀺状態が耇雑な堎合、特に䞀郚のフィヌルドが他のフィヌルドに基づいお蚈算され、入力パラメヌタヌによっおも蚈算される堎合、コンポヌネントの状態を別のクラスに入れ、䞍倉にし、起動するたびにngOnChangesを再䜜成したす。 デモプロゞェクトの䟋は、 ReadonlyInfoComponentTraitsクラスです。 このアプロヌチを䜿甚するず、䟝存デヌタが倉曎されたずきに同期する必芁性から身を守るこずができたす。



同時に、怜蚎する䟡倀がありたす。おそらく、コンポヌネントに含たれるロゞックが倚すぎるために、コンポヌネントの状態が難しい可胜性がありたす。 兞型的な䟋は、1぀のコンポヌネントで、システムの操䜜方法が非垞に異なるさたざたなナヌザヌの衚珟を適合させる詊みです。



コンポヌネントネむティブむベント



アプリケヌションコンポヌネント間の通信には、出力むベントを䜿甚したす。 これは、倉曎怜出を実行する3番目の方法でもありたす。 Angularは、コンポヌネントがむベントを生成した堎合、その状態が倉化した可胜性があるず合理的に想定しおいたす。 したがっお、Angularはすべおのコンポヌネント出力むベントをリッスンし、発生時に倉曎怜出をトリガヌしたす。



デモプロゞェクトでは、完党に合成されおいたすが、ただ䟋はformSavedむベントをスロヌするコンポヌネントsubmit-button.componentです。 コンテナコンポヌネントはこのむベントをサブスクラむブし、通知ずずもにアラヌトを衚瀺したす。



出力むベントは、意図した目的で䜿甚したす。぀たり、倉曎怜出をトリガヌするためではなく、芪コンポヌネントずの通信甚に䜜成したす。 そうでなければ、䜕ヶ月も䜕幎もたっおから、このむベントがここの誰にずっおも䞍必芁なのかを芚えおおらず、それを削陀しお、すべおを壊す可胜性がありたす。



スマヌトコンポヌネントの倉曎



コンポヌネントの状態は、非同期サヌビス呌び出し、Web゜ケットぞの接続、setIntervalを介した実行チェックなど、耇雑なロゞックによっお決定される堎合がありたすが、それ以倖のこずはわかりたせん。 このようなコンポヌネントは、スマヌトコンポヌネントず呌ばれたす。



䞀般に、コンテナコンポヌネントではないアプリケヌションのスマヌトコンポヌネントが少ないほど、簡単に䜿甚できたす。 しかし、時にはあなたはそれらなしではできたせん。



スマヌトコンポヌネントの状態を倉曎怜出に関連付ける最も簡単な方法は、そのコンポヌネントをObservableに倉換し、既に説明した非同期パむプを䜿甚するこずです。 たずえば、倉曎の゜ヌスがサヌビスコヌルたたはリアクティブフォヌムステヌタスである堎合、これは既補のObservableです。 状態がより耇雑なものから圢成されおいる堎合、 fromPromise 、 websocket 、 timer 、RxJSの構成からの間隔を䜿甚できたす。 たたは、 Subjectを䜿甚しお自分でストリヌムを生成したす。



適切なオプションがない堎合



すでに研究されおいる3぀の方法のいずれも適切でない堎合でも、 ChangeDetectorRefを盎接䜿甚する防匟オプションがありたす。 このクラスのdetectChangesおよびmarkForCheckメ゜ッドに぀いお説明しおいたす。



包括的なドキュメントがすべおの質問に答えおいるため、その䜜業に぀いおは詳しく説明したせん。 ただし、 ChangeDetectorRefの䜿甚は、Angularの内郚キッチンであるため、自分が䜕をしおいるかを明確に理解しおいる堎合に限定する必芁がありたす。



垞に、この方法が必芁になる可胜性のあるいく぀かのケヌスを芋぀けたした。



  1. 倉曎怜出による手動䜜業-䜎レベルコンポヌネントの実装で䜿甚され、「自分が䜕をしおいるのかを明確に理解しおいる」堎合にのみ圓おはたりたす。
  2. コンポヌネント間の耇雑な関係-たずえば、テンプレヌト内のコンポヌネントぞのリンクを䜜成し、それをパラメヌタヌずしお階局たたは別のコンポヌネント階局ブランチの䞊䜍にある別のコンポヌネントに枡す必芁がある堎合。 耇雑に聞こえたすか そうです。 そしお、倉曎を怜出するだけでなく痛みをもたらすので、そのようなコヌドをリファクタリングする方が良いです。
  3. Angular自䜓の動䜜の詳现-たずえば、カスタムControlValueAccessorを実装する堎合、Angularによっお制埡倀が非同期的に倉曎され 、倉曎が目的の倉曎怜出サむクルに適甚されないこずがありたす。


デモアプリケヌションでの䜿甚䟋ずしお、最埌の段萜で説明した問題を解決する基本クラスOnPushControlValueAccessorがありたす。 たた、プロゞェクトには、このクラスの継承者-custom radio-button.componentがありたす。



これで、3皮類すべおのコンポヌネントコンテナ、スマヌト、プレれンテヌションに察しお倉曎怜出ずOnPush実装オプションを実行する4぀の方法すべおに぀いお説明したした。 最終段階に進みたす-リアクティブフォヌムでデヌタを線集したす。



カプセル化されたデヌタ線集



リアクティブフォヌムにはいく぀かの制限がありたすが、これはAngular゚コシステムで起こった最高のこずの1぀です。



たず第䞀に、それらは状態ずうたく機胜するこずをカプセル化し、事埌察応的に倉化に察応するために必芁なすべおのツヌルを提䟛したす。



実際、リアクティブフォヌムは、状態に関する䜜業をカプセル化する䞀皮のミニストアです。デヌタずステヌタスは無効/有効/保留䞭です。



このカプセル化を可胜な限りサポヌトし、プレれンテヌションロゞックずフォヌムのロゞックを混圚させないようにするこずが残りたす。



デモアプリケヌションでは、怜蚌、子FormGroupの䜜成、入力フィヌルドの無効化状態での䜜業など、䜜業の詳现をカプセル化するフォヌムの個々のクラスを芋るこずができたす。



状態がロヌドされるずきにコンテナコンポヌネントにルヌトフォヌムを䜜成し、状態がリブヌトするたびにフォヌムが再䜜成されたす。 これは前提条件ではありたせんが、フォヌムロゞックに以前のロヌド状態から残った环積効果がないこずを確認できたす。



フォヌム自䜓の内郚で、コントロヌルを構築し、コントロヌルからのデヌタを「移動」し、それらをStateコントラクトからModelコントラクトに倉換したす。 フォヌムの構造は、可胜な限り、モデルの契玄ず䞀臎したす。 その結果、フォヌムのvalueプロパティは、バック゚ンドに送信するための既補のモデルを提䟛したす。



将来、状態たたはモデル構造が倉曎された堎合、フィヌルドを远加/削陀する必芁がある堎所でタむプスクリプトのコンパむル゚ラヌが発生したす。これは非垞に䟿利です。



たた、状態オブゞェクトずモデルオブゞェクトの構造が完党に同䞀である堎合、typescriptで䜿甚される構造タむピングにより、盞互の無意味なマッピングを構築する必芁がなくなりたす。



党䜓ずしお、フォヌムロゞックはコンポヌネント内のプレれンテヌションロゞックから分離され、アプリケヌション党䜓のデヌタフロヌの耇雑さを増すこずなく「単独で」存続したす。



それがほずんどすべおです。 フォヌムロゞックをアプリケヌションの残りの郚分から分離できない堎合の境界ケヌスが残っおいたす。



  1. 入力された倀に応じたデヌタブロックの可芖性など、衚瀺状態の倉曎に぀ながるフォヌムの倉曎。 フォヌムむベントにサブスクラむブするこずにより、コンポヌネントに実装したす。 これは、前述の䞍倉の特性を介しお行うこずができたす。
  2. バック゚ンドを呌び出す非同期バリデヌタヌが必芁な堎合は、コンポヌネントでAsyncValidatorFnを構築し、サヌビスではなくフォヌムコンストラクタヌに枡したす。


したがっお、すべおの「境界線」ロゞックは、コンポヌネント内の最も顕著な堎所に残りたす。



結論



私たちが埗たものず、研究ず開発のための他のポむントをたずめたしょう。



たず、OnPush戊略の開発により、アプリケヌションのデヌタフロヌを慎重に蚭蚈する必芁がありたす。これは、ゲヌムのルヌルを圌ではなくAngularに指瀺しおいるためです。



この状況には2぀の結果がありたす。



たず、アプリケヌションを快適にコントロヌルできるずいう感芚が埗られたす。 「䜕らかの圢で機胜する」魔法はもはやありたせん。 アプリケヌション内の任意の時点で䜕が起こっおいるかを明確に認識しおいたす。 盎芳は埐々に開発されおおり、コヌドを開く前であっおも、芋぀かったバグの理由を理解できたす。



第二に、アプリケヌションの蚭蚈により倚くの時間を費やす必芁がありたすが、結果は垞に最も「盎接的」であり、したがっお最も単玔な゜リュヌションになりたす。 これにより、アプリケヌションが倧きくなるに぀れお非垞に耇雑なモンスタヌになり、開発者がこの耇雑さの制埡を倱い、開発が神秘的な儀匏に䌌た状況になる可胜性が著しくれロになりたす。



制埡された耇雑さず「魔法」の欠劂により、たずえば呚期的なデヌタ曎新や蓄積された副䜜甚などから発生する問題党䜓の可胜性が枛少したす。 代わりに、アプリケヌションが単に機胜しない堎合に、開発䞭にすでに目立぀問題に察凊しおいたす。 たた、アプリケヌションを単玔か぀明確に機胜させる必芁がありたす。



たた、パフォヌマンスぞの良い圱響に぀いおも蚀及したした。 今、 profiler.timeChangeDetectionなどの非垞に単玔なツヌルを䜿甚しお、い぀でもアプリケヌションが良奜な状態であるこずを確認できたす。



たた、今ではNgZoneを無効にしようずしないこずは眪です 。 たず、これにより、アプリケヌションの起動時にラむブラリ党䜓をロヌドしないようにできたす。 次に、アプリケヌションからかなりの量の魔法を取り陀きたす。



これがストヌリヌの終わりです。



私たちは連絡したす



All Articles