ミックスジュースを作りながらRxJSを理解する

こんにちは、GxPの露木です！
この記事は[グロースエクスパートナーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/gxp)の20日目の記事です。

有志メンバーが集まって社内プロダクトを成長させるGrowthUs!という活動にて、
チーム内で「これは勉強していないつけが回ってきているぞ…」とProblemが出されたものがありました。

それは、__RxJS__…。

Tryとして、有識者からご教授頂くRxJS勉強会が行われました！
その時に分かったことと、ミックスジュースを作りながらチュートリアルを進める[RxJS-fruits](https://www.rxjs-fruits.com/subscribe)について書いていこうと思います。

# RxJSとは
>RxJS (Reactive Extensions for JavaScript) は、非同期またはコールバックベースのコード (RxJS Docs) の作成を容易にする observables を使用したリアクティブプログラミング用のライブラリ。
RxJS は Observable 型の実装を提供する。
※[Angularドキュメント](https://angular.jp/guide/rx-library) より引用

>リアクティブプログラミングは、データストリームと変化の伝播に関係する宣言型プログラミングパラダイムである。
※[Reactive programming - wikipedia](https://en.wikipedia.org/wiki/Reactive_programming) より引用

宣言型でプログラミングが記述でき、SQLライクに書けるので可読性が高く品質にばらつきにくい。
その一方、命令型のパラダイムに宣言型を持ってくるため命令型に慣れているエンジニアの初動学習コストが高く、
命令型本来の書き方よりも柔軟性が低いという面がある。

## RxJSの各クラス、関数について
__Observableクラス__
データを生成し、データを通知する。

__Observerクラス__
Observableのデータを処理する。
Observableの通知の各タイプに合う3つの関数（next, error, complete）を持つ。

__subscribe関数__
Observableのデータをsubscribe（購読）することによってObserverは受け取ったデータを処理する。

## RxJSを理解するための前提知識
RxJS概要で出てきた用語についての説明をまとめます。

### 命令的/宣言的
__命令的__
結果を「どうやって」取りに行くか、まで記述する。
ex) Java、JavaScript、C++
![スクリーンショット (3137).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303304/28a1c76c-5de8-c537-2b21-d21979bfd3dc.png)

__宣言的__
結果を取りに行くのに、「どうやって」の指定は特にしない。
ex) SQL、CSS
![スクリーンショット (3136).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303304/05bd74c7-ffef-ae95-876b-2f43733a4f76.png)

### Observerパターン

>プログラム内のクラスインスタンスで起きたイベント（事象）を、他のクラスインスタンスへ通知する処理で使われる、デザインパターンの一種。
[Observerパターン - wikipedia](https://ja.wikipedia.org/wiki/Observer_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3#%E5%87%BA%E5%85%B8)より引用

#### Observerパターンの各クラスについて
__Subject__
イベントを通知する側のインタフェース。
Observer（イベントを通知される側）のインタフェースの登録・削除・通知のメソッド書式の体裁を提供する。

__Observer__
イベントを通知される側のインタフェース。通知を受け取って処理をする。

__ConcreteObserver__
Observerの具象クラス。

※具象クラス：具象メソッド(実際に動作するプログラムが書かれたメソッド)が集まったクラス

__ConcreteSubject__
Subjectの具象クラス。

![アドベントカレンダー2022資料.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303304/190f284b-b7c8-c67f-f4f5-03dfc40cafd7.png)


# ミックスジュースを作る（RxJS-fruits）
概要を理解して、いよいよチュートリアルのミックスジュース作りに入っていきます！
スクリーンショットのように左側にコードの記述箇所、右側にベルトコンベアでフルーツを運んでミックスジュースを作る実行結果が表されています。

![スクリーンショット (3134).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303304/05d91d90-f347-6f8f-623a-6d7e311d0432.png)


## Level 1（Exercise: subscribe）
空のObservableをsubscribe（購読）して、Observableの中身を監視する。
Observableはsubscribe-functionで購読された場合のみアクティブとなりデータをストリーミングする。（＝ベルトコンベアーが動く）
```
// ベルトコンベアーを動かす
const conveyorBelt = EMPTY;
conveyorBelt.subscribe();
```


## Level 2（Exercise: subscribe-next）
ここではfruitsがObservable。
fruitsをsubscribe（購読）して、監視。
別クラスで定義されていると仮定したtoConveyorBelt関数で各フルーツをベルトコンベアーに乗せる。
```
// from関数で配列からobservableを作成
const fruits = from([
    "apple",
    "banana",
    "cherry"]);

fruits.subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Banana
・Cherry
```

## Level 3（Exercise: distinct）
pipe()で異なるRxJS演算子を実行することが出来る。
重複したフルーツがあり1つしかレシピに使わない場合は、同じ処理が2回流れない`distinct()`を用いる。

```
const fruits = from([
    "apple",
    "apple",
    "banana",
    "apple"]);

fruits.pipe(
	distinct()
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Banana
```
## Level 4（Exercise: take）
同じ種類のフルーツ4つの中でも2つしか使いたくない場合、最初の値から指定したカウント値までを取得する`take()`を用いる。
```
const fruits = from([
    "banana",
    "banana",
    "banana",
    "banana"]);

fruits.pipe(
	take(2)
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Banana
・Banana
```
## Level 5（Exercise: filter）
古いフルーツが紛れており新鮮なフルーツしか使いたくない場合、指定した条件に当てはまるデータを取得する`filter()`を用いる。
ここでは"old-"に当てはまらないフルーツを選択してベルトコンベアーに流している。
```
const fruits = from([
    "apple",
    "apple",
    "old-apple",
    "apple",
    "old-apple",
    "banana",
    "old-banana",
    "old-banana",
    "banana",
    "banana"]);

fruits.pipe(
	filter(p => !p.startsWith("old-"))
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Apple
・Apple
・Banana
・Banana
・Banana
```
## Level 6（Exercise: map）
汚れたフルーツがあり拭き取って使いたい場合は、Observable(fruits)が出力する各フルーツにプロジェクト関数を適用して、結果の値をObservableとして出力する`map()`を用いる。
ここではプロジェクト関数としてreplace()を用いて、"dirty-"を""に置き換えている。
```
const fruits = from([
    "dirty-apple",
    "apple",
    "dirty-banana",
    "banana"]);

fruits.pipe(
	map(p => p.replace("dirty-",""))
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Apple
・Banana
・Banana
```
## Level 7（Exercise: filter-map-take）
おさらい問題。
汚れたフルーツがあり拭き取って使いたい場合は、Observable(fruits)が出力する各フルーツにプロジェクト関数を適用して、結果の値をObservableとして出力する`map()`を用いる。
ここではプロジェクト関数としてreplace()を用いて、"dirty-"を""に置き換えている。
指定した条件に当てはまるデータを取得する`filter()`で、"old-"に当てはまらないフルーツを取得する。
最初の値から指定したカウント値までを取得する`take()`で最初の2つ（banana,apple）を取得する。
```
const fruits = from([
    "old-banana",
    "apple",
    "dirty-banana",
    "banana"]);

fruits.pipe(
	map(p => p.replace("dirty-","")),
    filter(p => !p.startsWith("old-")),
    take(2)
).subscribe(fruit => toConveyorBelt(fruit));
```
以下のように`take()`の代わりに、同じ処理が2回流れない`distinct()`を用いても同じ結果が得られる。
```
const fruits = from([
    "old-banana",
    "apple",
    "dirty-banana",
    "banana"]);

fruits.pipe(
	map(p => p.replace("dirty-","")),
    filter(p => !p.startsWith("old-")),
    distinct()
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Banana
```
## Level 8（Exercise: distinctUntilChanged）
フルーツに重複があれば飛ばして交互にフルーツが来て欲しい場合、最後にPushしたデータと重複していないか確認して取得する`distinctUntilChanged()`を用いる。
```
const fruits = from([
    "banana",
    "apple",
    "apple",
    "banana",
    "banana"]);

fruits.pipe(
	distinctUntilChanged()
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Banana
・Apple
・Banana
```
## Level 9（Exercise: skip）
最初の2つのフルーツは不要な場合、不要なデータをスキップすることが出来る`skip()`を用いる。
今回の場合は2つスキップする。
```
const fruits = from([
    "apple",
    "apple",
    "banana",
    "apple"]);

fruits.pipe(
	skip(2)
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Banana
・Apple
```
## Level 10（Exercise: skip-take-map）
おさらい問題。
汚れたフルーツがあり拭き取って使いたい場合は、Observable(fruits)が出力する各フルーツにプロジェクト関数を適用して結果の値をObservableとして出力する`map()`を用いる。
ここではプロジェクト関数としてreplace()を用いて、"dirty-"を""に置き換えている。
不要なデータをスキップすることが出来る`skip()`を用いてapple2つをスキップする。
最初の値から指定したカウント値までを取得する`take()`を用いてbananaのみ取得する。
```
const fruits = from([
    "dirty-apple",
    "apple",
    "dirty-banana",
    "dirty-banana",
    "apple"]);

fruits.pipe(
	map(p => p.replace("dirty-", "")),
    skip(2),
    take(1)
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Banana
```
## Level 11（Exercise: merge）
異なるObservableを1つのObservableにまとめることが出来る`merge()`を用いて、applesとbananasをマージする。
指定した条件に当てはまるデータを取得する`filter()`で、"old-"に当てはまらないフルーツを取得する。
```
const apples = from([
    "apple",
    "apple",
    "old-apple",
    "apple",
    "old-apple"]);

const bananas = from([
    "banana",
    "old-banana",
    "old-banana",
    "banana",
    "banana"]);

merge(apples,bananas).pipe(
	filter(p => !p.startsWith("old-"))
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Apple
・Apple
・Banana
・Banana
・Banana
```
## Level 12（Exercise: takeLast）
最後の値から3番目までのフルーツを取得したい場合、最後の値から指定したカウント値までを取得する`takeLast()`を用いる。
`take()`との使い分けは、例えばデータが100あった場合に1,2のデータを取りたい時は`take()`、99,100のデータを取りたい時は`takeLast()`を用いる。（可読性を高くする。）
```
const fruits = from([
    "apple",
    "apple",
    "banana",
    "apple",
    "banana"]);

fruits.pipe(
	takeLast(3)
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Banana
・Apple
・Banana
```
## Level 13（Exercise: skipLast）
最後の2つのフルーツが不要な場合、最後の値から不要なデータをスキップすることが出来る`skipLast()`を用いる。
```
const fruits = from([
    "apple",
    "apple",
    "banana",
    "apple",
    "banana"]);

fruits.pipe(
	skipLast(2)
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Apple
・Banana
```
## Level 14（Exercise: skipLast, skip & merge）
おさらい問題。
汚れたフルーツがあり拭き取って使いたい場合は、Observable(fruits)が出力する各フルーツにプロジェクト関数を適用して結果の値をObservableとして出力する`map()`を用いる。
ここではプロジェクト関数としてreplace()を用いて、"dirty-"を""に置き換えている。
`skip()`と`skipLast()`で不要なデータ（古いフルーツ）を飛ばす。
異なるObservableを1つのObservableにまとめることが出来る`merge()`を用いて、freshApplesとfreshBananasをマージする。
```
const apples = from([
    "apple",
    "dirty-apple",
    "apple",
    "old-apple",
    "old-apple"]);

const bananas = from([
    "old-banana",
    "old-banana",
    "dirty-banana",
    "dirty-banana",
    "dirty-banana"]);

const freshApples = apples.pipe(
    map(p => p.replace("dirty-", "")),
    skipLast(2)
);

const freshBananas = bananas.pipe(
    map(p => p.replace("dirty-", "")),
    skip(2)
);

merge(freshApples,freshBananas).pipe(
).subscribe(fruit => toConveyorBelt(fruit));
```

```
recipe
・Apple
・Apple
・Apple
・Banana
・Banana
・Banana
```
## Level 15（Exercise: zip & concatMap）
交互にフルーツを取得したい場合、2つObservableを接続し交互にpipe()にデータを渡す`zip()`、
渡された値の順番で処理する`concatMap()`を用いる。
```
const apples = from([
    "apple",
    "apple"]);

const bananas = from([
    "banana",
    "banana"]);

zip(apples,bananas).pipe(
    concatMap(p => p)	
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Banana
・Apple
・Banana
```
## Level 16（Exercise: repeat）
フルーツの個数が足りない場合、指定した数値分、値を取得する`repeat()`を用いる。
```
const fruits = from(["apple"]);

fruits.pipe(
	repeat(3)
).subscribe(fruit => toConveyorBelt(fruit));
```
```
recipe
・Apple
・Apple
・Apple
```

# チュートリアル終了！最後に
RxJSとは何ぞやというところから始まった勉強会でしたが、まず概要を理解するまでの前提知識が不足していたことに気が付きました。（命令的/宣言的、Observerパターン等）
概要をいきなり理解しようとする前に、これからは出てくる用語で分からないものがあればひとつひとつかみ砕くことを意識して理解できるようになりたいです。
前提知識・チュートリアルを経て、チームで「分からないことが分からない」状態から何となくでも分かる状態になりました！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303304/55308186-c65a-8170-6bc0-53cd0e654fbc.png)



# 参考資料URL
https://angular.jp/guide/rx-library

https://www.rxjs-fruits.com/subscribe

https://ja.wikipedia.org/wiki/Observer_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

https://jp.quora.com/%E5%91%BD%E4%BB%A4%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%A8%E5%AE%A3%E8%A8%80%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E9%81%95