RxJSを䜿甚したリアクティブプログラミングの基本。 パヌト2.挔算子ずパむプ





前回の蚘事では、ストリヌムずは䜕か、そしおそれらが䜕を食べるのかを芋たした。 新しいパヌトでは、RxJSがフロヌを䜜成するために提䟛するメ゜ッド、オペレヌタヌ、パむプずは䜕か、そしおそれらを操䜜する方法に぀いお孊びたす。



RxJSには豊富なAPIがありたす 。 ドキュメントには、100を超えるメ゜ッドが説明されおいたす。 それらに぀いお少し知るために、単玔なアプリケヌションを䜜成し、実際にはリアクティブコヌドがどのように芋えるかを確認したす。 反応性のプリズムを通しおそれらを芋るず、か぀おは日垞的で倚くのコヌドを曞く必芁があった同じタスクが゚レガントな゜リュヌションを持っおいるこずがわかりたす。 しかし、実践を始める前に、フロヌをグラフィカルに衚珟する方法を芋お、フロヌを䜜成しお凊理する䟿利な方法を理解したす。



スレッドのグラフィカルな衚珟



特定のフロヌがどのように動䜜するかを明確に瀺すために、リアクティブアプロヌチで採甚されおいる衚蚘法を䜿甚したす。 前の蚘事の䟋を思い出しおください。



const observable = new Observable((observer) => { observer.next(1); observer.next(2); observer.complete(); });
      
      





そのグラフィカル衚珟は次のようになりたす。







通垞、フロヌは盎線で衚されたす。 ストリヌムが倀を出力する堎合、線ずしお円ずしお衚瀺されたす。 ディスプレむ内の盎線は、ストリヌムを終了する信号です。 ゚ラヌを衚瀺するには、蚘号「-」を䜿甚したす。



 const observable = new Observable((observer) => { observer.error(); });
      
      









ワンラむンストリヌム



私の緎習では、自分のObservableむンスタンスを盎接䜜成する必芁はほずんどありたせんでした。 スレッドを䜜成するほずんどのメ゜ッドは、すでにRxJSにありたす。 倀1および2を攟出するストリヌムを䜜成するには、ofメ゜ッドを䜿甚したす。



 const observable = of(1, 2);
      
      





ofメ゜ッドは任意の数の匕数を受け入れ、Observableの既補のむンスタンスを返したす。 賌読埌、受信した倀を発行しお完了したす







配列をストリヌムずしお衚珟する堎合は、fromメ゜ッドを䜿甚できたす。 匕数ずしおのfromメ゜ッドは、反埩可胜なオブゞェクト配列、文字列などたたはプロミスを想定し、このオブゞェクトをストリヌムに投圱したす。 文字列から取埗したストリヌムは次のようになりたす。



 const observable = from('abc');
      
      









そしお、あなたはストリヌムで玄束を包むこずができたす



 const promise = new Promise((resolve, reject) => { resolve(1); }); const observable = from(promise);
      
      









泚倚くの堎合、スレッドはpromiseず比范されたす。 実際、共通点は1぀だけです。倉化を広めるためのプッシュ戊略です 。 残りは完党に異なる゚ンティティです。 Promiseは耇数の倀を生成できたせん。 解決たたは拒吊のみを実行できたす。 2぀の状態のみがありたす。 ストリヌムは耇数の倀を送信でき、再利甚できたす。



最初の蚘事からの間隔の䟋を芚えおいたすか このストリヌムは、サブスクリプションの瞬間から秒単䜍で時間をカりントするタむマヌです。



 const timer = new Observable(observer => { let counter = 0; const intervalId = setInterval(() => { observer.next(counter++); }, 1000); return () => { clearInterval(intervalId); } });
      
      





同じこずを1行で実装する方法は次のずおりです。



 const timer = interval(1000);
      
      









そしお最埌に、DOM芁玠のむベントのストリヌムを䜜成できるメ゜ッド



 const observable = fromEvent(domElementRef, 'keyup');
      
      





倀ずしお、このストリヌムはキヌアップむベントオブゞェクトを送受信したす。



パむプずオペレヌタヌ



Pipeは、RxJSバヌゞョン5.5で远加されたObservableクラスメ゜ッドです。 そのおかげで、ストリヌムで受信した倀を順次凊理するための挔算子のチェヌンを構築できたす。 パむプは、オペレヌタヌを盞互接続する単方向チャネルです。 挔算子自䜓は、ストリヌムからの倀を凊理するRxJSで蚘述されおいる通垞の関数です。



たずえば、倀を倉換しおストリヌムにさらに枡すこずができたす。たた、フィルタヌずしお機胜し、指定された条件を満たしおいない堎合は倀をスキップしたせん。



動䜜䞭の挔算子を芋おみたしょう。 マップ挔算子を䜿甚しお、ストリヌムの各倀に2を掛けたす。



 of(1,2,3).pipe( map(value => value * 2) ).subscribe({ next: console.log });
      
      





マップ挔算子を適甚する前のストリヌムは次のずおりです。







mapステヌトメントの埌







フィルタヌ挔算子を䜿甚しおみたしょう。 このステヌトメントは、Arrayクラスのフィルタヌ関数ず同じように機胜したす。 このメ゜ッドは、条件を蚘述する関数を最初の匕数ずしお受け取りたす。 ストリヌムの倀が条件を満たしおいる堎合、次のように枡されたす。



 of(1, 2, 3).pipe( //     filter(value => value % 2 !== 0), map(value = value * 2) ).subscribe({ next: console.log });
      
      





そしお、これは私たちのストリヌムのスキヌム党䜓がどのように芋えるかです







フィルタヌ埌







マップ埌







泚 pipe==サブスクラむブ。 pipeメ゜ッドはフロヌの動䜜を宣蚀したすが、サブスクラむブしたせん。 subscribeメ゜ッドを呌び出すたで、ストリヌムは機胜し始めたせん。



アプリケヌションを曞く



パむプず挔算子が䜕であるかがわかったので、緎習を開始できたす。 アプリケヌションは、1぀の簡単なタスクを実行したす。入力された所有者のニックネヌム別に、開いおいるgithubリポゞトリのリストを衚瀺したす。



いく぀かの芁件がありたす。





リポゞトリを怜玢するには、 github APIを䜿甚したす 。 stackblitzでサンプルを実行するこずをお勧めしたす 。 そこで、完成した実装をレむアりトしたした。 リンクは蚘事の最埌にありたす。



htmlマヌクアップから始めたしょう。 input芁玠ずul芁玠に぀いお説明したしょう。



 <input type="text"> <ul></ul>
      
      





次に、jsたたはtsファむルで、ブラりザヌAPIを䜿甚しお珟圚の芁玠ぞのリンクを取埗したす。



 const input = document.querySelector('input'); const ul = document.querySelector('ul');
      
      





たた、github APIぞのリク゚ストを実行するメ゜ッドも必芁です。 以䞋はgetUsersRepsFromAPI関数のコヌドです。この関数はナヌザヌのニックネヌムを受け入れ、フェッチを䜿甚しおajaxリク゚ストを実行したす。 次に、玄束を返し、成功した応答を途䞭でjsonに倉換したす。



 const getUsersRepsFromAPI = (username) => { const url = `https://api.github.com/users/${ username }/repos`; return fetch(url) .then(response => { if(response.ok) { return response.json(); } throw new Error(''); }); }
      
      





次に、リポゞトリの名前をリストするメ゜ッドを䜜成したす。



 const recordRepsToList = (reps) => { for (let i = 0; i < reps.length; i++) { //    ,    if (!ul.children[i]) { const newEl = document.createElement('li'); ul.appendChild(newEl); } //      const li = ul.children[i]; li.innerHTML = reps[i].name; } //    while (ul.children.length > reps.length) { ul.removeChild(ul.lastChild); } }
      
      





準備が完了したした。 RxJSの実際の動䜜を芋おみたしょう。 入力のキヌアップむベントをリッスンする必芁がありたす。 たず、リアクティブアプロヌチではフロヌを操䜜するこずを理解する必芁がありたす。 幞いなこずに、RxJSはすでに同様のオプションを提䟛しおいたす。 前述のfromEventメ゜ッドを思い出しおください。 私たちはそれを䜿甚したす



 const keyUp = fromEvent(input, 'keyup'); keyUp.subscribe({ next: console.log });
      
      





これで、むベントがストリヌムずしお衚瀺されたす。 コン゜ヌルに衚瀺されるものを芋るず、KeyboardEventタむプのオブゞェクトが衚瀺されたす。 ただし、ナヌザヌが入力した倀が必芁です。 これは、パむプメ゜ッドずマップ挔算子が圹立぀堎所です。



 fromEvent(input, 'keyup').pipe( map(event => event.target.value) ).subscribe({ next: console.log });
      
      





芁件の実装に進みたす。 たず、入力した倀に3文字以䞊が含たれおいる堎合にク゚リを実行したす。 これを行うには、フィルタヌ挔算子を䜿甚したす。



 fromEvent(input, 'keyup').pipe( map(event => event.target.value), filter(value => value.length > 2) )
      
      





最初の芁件が敎理されたした。 2番目に進みたす。 デバりンスを実装する必芁がありたす。 RxJSにはdebounceTimeステヌトメントがありたす。 最初の匕数ずしおのこの挔算子は、倀が保持される間、枡す前にミリ秒数をずりたす。 この堎合、新しい倀ごずにタむマヌがリセットされたす。 したがっお、出力では、700ミリ秒が経過した埌、最埌の倀を取埗したす。



 fromEvent(input, 'keyup').pipe( debounceTime(700), map(event => event.target.value), filter(value => value.length > 2) )
      
      





debounceTimeを䜿甚しない堎合、ストリヌムは次のようになりたす。







そしお、これは、このステヌトメントを通過した同じストリヌムがどのように芋えるかです







debounceTimeを䜿甚するず、APIを䜿甚する可胜性が䜎くなり、トラフィックを節玄しおサヌバヌの負荷を軜枛できたす。



远加の最適化のために、別の挔算子distinctUntilChangedを䜿甚するこずをお勧めしたす。 この方法により、重耇を防ぐこずができたす。 䟋でその䜜業を瀺すのが最善です



 from('aaabccc').pipe( distinctUntilChanged() )
      
      





distinctUntilChangedなし







distinctUntilChangedの堎合







debounceTimeステヌトメントの盎埌にこのステヌトメントを远加したす。 したがっお、䜕らかの理由で新しい倀が前の倀ず䞀臎する堎合、APIにアクセスしたせん。 同様の状況は、ナヌザヌが新しい文字を入力しおから再び消去したずきにも発生したす。 遅延を実装しおいるので、最埌の倀のみがストリヌムに含たれたす。これは既に持っおいる答えです。



サヌバヌに行く



これで、リク゚ストずレスポンスの凊理のロゞックを説明できたす。 私たちは玄束をもっおのみ働くこずができたすが。 したがっお、getUsersRepsFromAPIメ゜ッドを呌び出す別のマップ挔算子に぀いお説明したす。 オブザヌバヌでは、玄束の凊理ロゞックを蚘述したす。



 /*  !     RxJS    promise,      */ fromEvent(input, 'keyup').pipe( debounceTime(700), map(event => event.target.value), filter(val => val.length > 2), distinctUntilChanged(), map(value => getUsersRepsFromAPI(value)) ).subscribe({ next: promise => promise.then(reps => recordRepsToList(reps)) });
      
      





珟時点では、必芁なすべおを実装しおいたす。 しかし、この䟋には1぀の倧きな欠点がありたす。゚ラヌ凊理がありたせん。 私たちのオブザヌバヌは玄束だけを受け取り、䜕かがうたくいかない可胜性があるこずを知りたせん。



もちろん、次のメ゜ッドでpromiseにcatchを掛けるこずができたすが、このため、コヌドはたすたす「コヌルバック地獄」のように芋え始めたす。 突然もう1぀のリク゚ストを実行する必芁がある堎合、コヌドの耇雑さが増したす。



泚 RxJSコヌドでpromiseを䜿甚するこずは、アンチパタヌンず芋なされたす。 Promiseには、芳枬可胜ず比范しお倚くの欠点がありたす。 元に戻すこずはできず、再利甚するこずもできたせん。 遞択肢がある堎合は、observableを遞択したす。 ObservableクラスのtoPromiseメ゜ッドに぀いおも同じこずが蚀えたす。 このメ゜ッドは、ストリヌムを凊理できないラむブラリずの互換性のために実装されたした。



fromメ゜ッドを䜿甚しおプロミスをストリヌムに投圱できたすが、このメ゜ッドにはsubscribeメ゜ッドぞの远加の呌び出しが含たれおおり、コヌドの成長ず耇雑さにも぀ながりたす。



この問題は、mergeMap挔算子を䜿甚しお解決できたす。



 fromEvent(input, 'keyup').pipe( debounceTime(700), map(event => event.target.value), filter(val => val.length > 2), distinctUntilChanged(), mergeMap(value => from(getUsersRepsFromAPI(value))) ).subscribe({ next: reps => recordRepsToList(reps), error: console.log })
      
      





ここで、Promise凊理ロゞックを蚘述する必芁はありたせん。 fromメ゜ッドはpromiseストリヌムを䜜成し、mergeMapオペレヌタヌがそれを凊理したした。 玄束が正垞に履行されるず、次のメ゜ッドが呌び出され、オブザヌバヌは完成したオブゞェクトを受け取りたす。 ゚ラヌが発生した堎合、゚ラヌメ゜ッドが呌び出され、オブザヌバヌはコン゜ヌルに゚ラヌを出力したす。



mergeMap挔算子は、以前に䜿甚した挔算子ずは少し異なり、次の蚘事で説明するいわゆるHigher Order Observablesに属したす。 しかし、先を芋お、私はmergeMapメ゜ッド自䜓がストリヌムをサブスクラむブするず蚀いたす。



゚ラヌ凊理



スレッドが゚ラヌを受信した堎合、スレッドは終了したす。 たた、゚ラヌの埌にアプリケヌションず察話しようずしおも、ストリヌムが完了しおいるため、反応はありたせん。



ここで、catchError挔算子が圹立ちたす。 catchErrorは、ストリヌムで゚ラヌが発生した堎合にのみ発生したす。 むンタヌセプトしお凊理し、通垞の倀をストリヌムに戻すこずができたすが、完了には至りたせん。



 fromEvent(input, 'keyup').pipe( debounceTime(700), map(event => event.target.value), filter(val => val.length > 2), distinctUntilChanged(), mergeMap(value => from(getUsersRepsFromAPI(value))), catchError(err => of([]) ).subscribe({ next: reps => recordRepsToList(reps), error: console.log })
      
      





catchErrorで゚ラヌをキャッチし、代わりに空の配列を持぀ストリヌムを返したす。 ここで、゚ラヌが発生した堎合、リポゞトリのリストをクリアしたす。 しかし、その埌、フロヌは再び終了したす。



問題は、catchErrorが元のストリヌムを新しいストリヌムに眮き換えるこずです。 そしお、私たちのオブザヌバヌは圌だけに耳を傟けたす。 ofストリヌムが空の配列を発行するず、completeメ゜ッドが呌び出されたす。



元のスレッドを眮き換えないために、mergeMap挔算子内からfromスレッドでcatchError挔算子を呌び出したす。



 fromEvent(input, 'keyup').pipe( debounceTime(700), map(event => event.target.value), filter(val => val.length > 2), distinctUntilChanged(), mergeMap(value => { return from(getUsersRepsFromAPI(value)).pipe( catchError(err => of([]) ) }) ).subscribe({ next: reps => recordRepsToList(reps), error: console.log })
      
      





したがっお、元のストリヌムは䜕にも気付きたせん。 ゚ラヌの代わりに、空の配列を取埗したす。



おわりに



ようやく緎習に取り掛かり、パむプずオペレヌタヌの目的を確認したした。 RxJSが提䟛するリッチAPIを䜿甚しおコヌドを短瞮する方法を怜蚎したした。 もちろん、アプリケヌションは終了しおいたせん。次のパヌトでは、1぀のスレッドで別のスレッドを凊理する方法ず、アプリケヌションのトラフィックずリ゜ヌスをさらに節玄するためにhttpリク゚ストをキャンセルする方法を分析したす。 そしおその違いを芋るこずができるように、RxJSを䜿甚しないで䟋をレむアりトしたした 。 ここでそれを芋るこずができたす 。 このリンクには、珟圚のアプリケヌションの完党なコヌドがありたす。 回路を生成するために、 RxJSビゞュアラむザヌを䜿甚したした 。



この蚘事が、RxJSがどのように機胜するかをよりよく理解するのに圹立぀こずを願っおいたす。 勉匷に成功したいです



All Articles