RxJを使用してアプリケーションコンポーネントをバインドする

「通信」コンポーネントの方法



最新のWebアプリケーションのクライアント部分は、モジュール性なしでは想像するのが難しく、モジュール間でのデータ交換または単なる通信が含まれます。 このコミュニケーションの編成方法は、プロジェクトの複雑さとプロジェクトで使用されるテクノロジーによって異なります。



最初に思い浮かぶのは、名前付きイベントの発行とサブスクライブです。 コンポーネントの1つがイベントを「air」に送信し、残りはこの「air」をリッスンし、必要なメッセージをキャッチします。 このアイデアは非常にシンプルであり、長い間実証されてきました。



画像








これは、誰もが全員とメッセージを交換できるカフェのWI-FIをやや連想させますが、同時に「エーテル」の存在を確認し、宛先が指定された人にのみメッセージを送信するルーター(ディスパッチャー)があります。



このような組織では、たとえば、コンポーネントの弱い結合を「無料」で取得できます。 その欠点は、コンポーネントの数が増え、それに応じてイベントの数が増えると、イベントの名前と、誰がどのイベントを正しく機能させる必要があるかを追跡することが難しくなることです。 名前空間が表示され、「Event1」などのイベント名が「Application_Status1.Component2.Event1」に変わります。 そして、そのような組織では絶対に不可能なことは、イベントを作成することです。 たとえば、「2つのイベントAの後にイベントBが発生したときに何かを行う」という要件は、イベントからの最新データとイベント自体のカウンターを格納する大量のローカル変数に変換されます。



Promiseを使用すると、イベントが少し簡単になり、一連のイベントを整理できるようになり、データフローを整理する最初のステップになります。



コンポーネント間の通信を編成する別の方法は、コンポーネント間で「ワイヤ」を引き伸ばして、相互に「発言」するものがあるコンポーネントのみが接続されるようにすることです。 この接合方法を視覚的に提示するには、プリント基板を見てください。



画像








ここで、各コンポーネントは、それに必要な他のコンポーネント、トラック(「ワイヤ」)、または単に「ストリーム」に接続され、それに沿って信号が送信されます。 ここで、コンポーネントが機能するために必要なデータの種類を想像するために、ブラックボックスに入ってイベントのサブスクリプションを探す必要はありません。 ストリームのコンポーネントと、データが途中でどのように変換されるかを見てください。 そのような組織のイベントに名前を付ける必要はまったくありませんが、データを運ぶストリームには名前が必要です。 この場合、イベントをリンクするタスクは、それらを含むストリームをリンクすることになります。



スレッドの実装



言葉から行為に移るには、「ワイヤー」の実装を選択する必要があります。 私にとって、 RxJSは、データストリームを作成および構成できるモジュールライブラリであり、そのような実装になりました。 Rxで使用されるアプローチは.NETに登場し、そこから多くの一般的な言語に移植されました。 実装されたロジックの複雑さに応じて、RxJS全体とその個別のモジュールをプロジェクトに接続できます。 重要な概念を考えてみましょう。



Rxで作成されたストリームはObservableパターンを実装し、同じ名前のインターフェイスから継承します。つまり、各ストリームを「リスニング」できます。 これは、引数としてObserverをとるsubscribeメソッドを使用して実装されます。



observableStream.subscribe(someObserver)
      
      





最も単純なケースでは、Observerは単一の引数、つまりストリームから送信されたメッセージを受け取る関数です。 メッセージは、単純な値または複雑なオブジェクトのいずれかです。



 function someObserver(streamEvent){ console.log('Received ' + streamEvent) }
      
      





たとえば、ストリームを使用してDOMイベントを処理できます。 RxJS-jQueryプラグインを使用してDOMイベントのサブスクリプションを整理するのが最も便利です。



たとえば、ボタンのクリックに応答してDOMデータとしてイベントを送信するストリームは、次のように作成できます。



 var myAwesomeButtonClick = $('#my-awesome-button').onAsObservable('click')
      
      





これで、ボタンクリックを処理する必要がある場合、ストリームmyAwesomeButtonClickを別のコンポーネントに転送できます。 任意のストリームを編成するには、Rx.Subjectを使用します。これはObservableパターンも実装しますが、Observableの機能に加えて、任意のメッセージをストリームにスローできます。これには、onNextメソッドが使用されます。



 subjectStream.onNext('new message')
      
      





これで、他の人にメッセージを送信したいコンポーネントは作成されたストリームを返し、メッセージを受信したいコンポーネントはストリームを受信し、そこからメッセージをサブスクライブする必要があります。



画像








これで、コンポーネント1でイベントが発生するとすぐにonNextが呼び出され、コンポーネント2はこのイベントをすぐに受信して処理します。

複雑になると、各コンポーネントは複数のスレッドを返したり受け取ったりし、スレッドのような種類のチップに変わります。



画像








しかし、これはすべて約束の助けを借りて組織化することができます。 ここでRxJsストリームを使用する利点は何ですか?



アプリケーションで発生するすべてのことは、データストリームとして表すことができます。





そして、これはすべて統一された方法で表現できるため、統一された方法で作業できることを意味します。コンポーネントの場合は、データストリームであるため、イベントがどこから来て、どのようなイベントであるかに違いはありません。



ストリームイベントの処理、条件、および副作用



多くの場合、1つのコンポーネントは、元は入力ストリームにあるデータを必要とせず、何らかの方法で処理または再フォーマットする必要があります。 これを説明する最も簡単な方法は、例を使用することです。



 var inputChanges = $('input.name').onAsObservable('change, keyup, paste')
      
      





inputChangesストリームが作成され、入力フィールドの一部のイベントに応答し、データとしてDOMイベントを送信しますが、ルールとして、コンポーネントの内部ロジックにはDOMイベントは必要ありませんが、特定の値があり、いくつかのルールを満たすことが望ましいです。 inputChangesストリームのイベント処理を実装する新しいスレッドを作成します。



 var inputValueChanges = inputChanges .map(function(event){ return $(event.target).val() }) .distinctUntilChanged() .where(isValidName) .do(someAction)
      
      





新しいinputValueChangesストリームは、入力フィールドに値を返します。フィールドの値が実際に変更され、何らかのフォーマットを満たしている場合にのみ、新しいイベントが発生します。 さらに詳しく分析しましょう。



 .map(function(event){ return $(event.target).val() })
      
      





mapメソッド(別名select)は、ハンドラー関数を取ります。ハンドラー関数は、イベントを引数として受け取り、後でストリームイベントとして使用される値を返します。



 .distinctUntilChanged()
      
      





それを通過する値を監視し、イベントの値が前のイベントの値と変わらない場合、それ以上渡しません。



 .where(isValidName)
      
      





whereメソッド(別名フィルター)とdistinctUntilChangedでは、さらにいくつかのイベントをスキップすることはできませんが、条件として、これらのイベントが特定の要件を満たしているかどうかをチェックする関数を取ります。



 .do(someAction)
      
      





do (またはdoAction)は、ストリーム内のイベント自体に影響を与えることなく、副作用を実装します。 someAction関数は、mapの場合のように、単一の引数-イベントを取ります。



ストリームレイアウト



1つのコンポーネントが何らかのアクションを実行するために、他の2つのコンポーネントからのデータを予期することがよくあります。 たとえば、「サマリー」コンポーネントへの旅行の価格を表示するには、「カレンダー」および「ルート」コンポーネントからデータを取得し、コンポーネントのいずれかで何かが変更されたらすぐにそれを更新する必要があります。



画像








2つのストリームからデータを取得するには、それらを1つのストリームに結合し、両方のデータを含めることができます。 上記の例では、 combinateLatestメソッドを使用できます



 var routeAndDateChangesStream = routeChangesStream .combineLatest(dateChangesStream, function(route, date){ return { date: date, route: route } })
      
      





composeLatestメソッドは、2つ(またはそれ以上)のスレッドを1つに結合します。 結合されたフローのそれぞれに少なくとも1つのイベントがある場合、結果のストリームのイベントは、結合されたフローの1つでイベントが発生したときに発生します。 最初の引数composeLatestは、マージするストリームを受け入れます。2番目は、イベントからのデータをマージする方法を決定する関数です。 いずれかのスレッドでイベントが発生すると、マージのために最後のイベントが残りのスレッドから取得されます。



画像








最近のイベントを結合することに加えて、発生順にイベントをペアで結合する必要がある場合があります。 たとえば、いくつかのアクションは2つのAjaxリクエストを生成し、両方のリクエストがデータを返すときに反応する必要があり、互いに一致するリクエストからのデータのみを結合する必要があります。 このような状況では、ストリームからの最新のイベントではなく、シーケンス番号が一致するイベントを結合するzipメソッドを使用すると便利です。



 var routeAndDateChangesStream = response1Stream .zip(response2Stream, function(data1, data2){ return _.extend(data1, data2) })
      
      





画像






最後に、ストリームのデータを結合する必要はないが、コンポーネントが異なるストリームから同じ情報を受け取る場合、たとえば、ページに2つのカレンダーがあり、1つは1年、2つ目は次の2週間、両方のカレンダーが日付を「サマリー」コンポーネントに渡す場合。 ここでは、2つのストリームを1つに結合するだけです。



 var dataChangesStream = bigCalendarDateChange.merge(miniCalendarDateChanges)
      
      





ここでは、両方のカレンダーからのイベントが結果のストリームに分類され、それらを受信するコンポーネントは、選択が常に1つのコンポーネントでのみ発生したかのようにそれらを処理できます。



画像








テスト中



チップの種類に応じたコンポーネントの構成には、もう1つの利点があります-テスト容易性。



コンポーネントが一連のストリームを介してすべての着信データを受信し、一連のストリームを介してデータを送信する場合、このようなコンポーネントのテストは大幅に簡素化されます。 空の着信ストリームのセットを作成し、着信としてコンポーネントに渡すだけで十分です。 さらに、イベントの特定の組み合わせをストリームに送信すると、コンポーネントが正しく動作するかどうか、および正しいデータを送信ストリームに送信するかどうかを簡単に確認できます。



All Articles