LoginSignup
15
9

More than 3 years have passed since last update.

Reactive Programming with JavaScript

Last updated at Posted at 2020-05-31

はじめに

JavaScriptを触るなかで関数型リアクティブプログラミング(FRP)について知り、面白そうと調べましたが考え方のベースとなる部分を理解することに苦労したので記録として残します。

What is Reactive?

アプリケーションは多くのモジュールによって構成されています。多くの場合、そのモジュール達はデータによって繋がっています。あるモジュールの値が更新されると、他のモジュールの値も更新されるようなシーンです。
Untitled Diagram.jpg

例としてAmazonのようなオンラインショッピングサービスを提供するアプリケーションを挙げ、アプリケーション内の『カート』を担当するモジュールと、『請求書』を担当するモジュールの2つの関係に注目します。例えばユーザーがカートに商品を追加したとき、請求書は商品の金額に応じて自動で更新されます。つまり図の矢印はデータの流れを表していて、カートのデータが更新されると請求書のデータも更新されることを表現しています。このとき請求書はプログラム的にどのように更新されるのか、Cartモジュール側がInvoiceモジュールの値を更新しているのか、Invoiceモジュール側がCartモジュールの値の変動を監視して自力で変更しているのか考えてみます。Reactiveなプログラミングの場合、後者がそれと言えます。

Reactive ProgrammingとはInvoiceモジュール側がCartモジュールの値の変動を監視して自力で変更しているようなプログラム

つまり外部のモジュールの変更を監視し、自身のデータを更新します。前者とは違い、モジュールの値の更新を他のモジュールに依存していません。ちなみに前者のようなプログラミングをAndré Staltz(cycle.jsを作ったエンジニア)はモジュールの外部のデータに対する受動的な姿勢からPassive Programmingと名付けています。次に何故このリアクティブプログラミングが近年注目されているのかを示していきます。

Why Reactive Programming?

下の図のように6つのモジュールから構成されるアプリケーションがあるとします。
Untitled Diagram (3).jpg
このアプリケーションの挙動を理解しようとしたとき、各モジュールのコードを読んでいくことになると思います。その中でCのモジュールを読んでいるとします。先ほどのPassive Programmingの場合、Cのモジュール内で扱っているデータがどこから来ているのかが分かりません。つまりCのモジュールを見ただけではCの挙動を理解できないのです。Aのモジュールを見ないと理解できません。加えてAのようなモジュールがどこに存在するか探す作業もあります。
Reactive Programmingの場合、CのモジュールはAのモジュールを監視していて、Aが変更されるたびにCのデータは更新されます。従ってCを見るだけで、Cの挙動が理解できるわけです。つまり分かりやすいというメリットがあります。これ以外にも先ほどのアマゾンの例と似ていますが検索候補(search suggestion)のようにinputの値が更新されるたびにサーバーにアクセスすることなくreactiveに検索候補を挙げてくれるプログラムを実現できるというメリットもあります。(次のエクセルの例も参考にしてみて下さい。)
ただここで疑問になるのが、他のモジュールのイベントを監視し、反応するリアクティブプログラミングはどうやって実装されるのか、です。

What is Reactive Programming?

モジュール自身で他のモジュールのデータの変更に対応しようとしても、他のモジュールのデータをフェッチするような関数を作成し他のモジュール側で実行してもReactiveとは言えません。Reactiveなプログラミングを実装するのは結構大変なのです。では、どうやって実装するのか、どういうものがReactive Programmingと言えるのか。まずはリアクティブプログラミングの具体的なイメージから入りましょう。
Reactive Programmingを用いたアプリケーションとして有名なものにエクセルがあります。エクセルをイメージすると仕組みが理解できると思います。
Untitled Diagram (4).jpg
例えば上の図を見たとき赤丸の部分が何を意味しているのか『-2』という値だけでは分かりません。エクセルではセルをクリックすると、どこのデータをフェッチしてきているのか一目で分かります。
Untitled Diagram (5).jpg
このように、どのセルからデータをフェッチしているのか一目で分かります。また『B3』のセルのデータを変更すると自動で赤丸部分の値が更新されます。見れば分かりますが、赤丸部分のデータの更新は『B3』『B9』のセルに依存しておらず、そのため赤丸部分の内容を見るだけで挙動が理解できます。このセルたちをモジュールに置き換えたものが関数型リアクティブプログラミング(FRP)であり、これをコードに置き換えることで実装できます。それでは実際にコードで実装していきましょう。

How to Reactive Programming?

今回はrxjsというライブラリを用いてリアクティブプログラミングを実装していきます。rxjsはコードを分かりやすいものとするために様々な役割を用語として定義しています。rxjsを用いてリアクティブプログラミングを実装するのには理解が必要な用語がいくつかあります。

Untitled Diagram (11).jpg

observable

いつ更新されるか分からないデータを含め全てのデータをストリーム(データの流れ)とし、そのデータのストリーム自体をラップするものをRxjsではobservableと呼ばれます。エクセルのセルのようにデータをラップするものです。例えばDBのデータをラップすることで、DBが変化するごとにリロードせずとも対応できます。ただし関数が呼び出されない限り動かないように、observableもsubscribeされない限り何も返しません。また複数の返り値を返すことができます。

subscribe

observable内にあるデータが更新されたとき、observerに対して知らせる役割を持つ。Youtubeのsubscribe機能が分かりやすいと思います。ユーザーは自分の好きなYouTuberに対してsubscribeすることでYouTuber(observable)の動画投稿の動き(データストリーム)を監視(listen)することができます。よく購読と訳されるのは、恐らくobservableをsubscribeすることで初めて購読(中身のデータを扱えることが)できるためだと思います。
observableをsubscribeするとsubscriptionオブジェクトを返し、これをunsubscribeしない限りobserverはobservableからデータを受け取り続けます。

pipe

実際にobsevableのイベントに対してreactする部分。operatorと呼ばれる、データに対し予め決められた操作のできる関数を用いてデータを操作(manipulate)します。

observer

新しいデータをobservableから受け取ったときに、それに対して処理等を行うのがobserverオブジェクトです。observableからのデータのconsumer。
3種類のコールバックを用意することができます。observableからの通知のようなものです。

  • nextメソッドは新しいデータをobservableが受け取るたびに実行されるメソッド。
  • errorメソッドはobservableがエラーを投げたときに実行されるメソッド。
  • completeメソッドがobservableの更新が完了したときに実行されるメソッド。

エクセルのセルに対するデータの入力、更新に終わりがないようにobservableが決して完了しないケースもあります。つまりcompleteメソッドが必要ないことがあります。

subject

observableと似ているが、多くのobserverに対して変更をマルチキャストすることができます。つまり多くのobserverを持つことができるとも言え、Reactで言えばReduxと概念は似ていると思います。

rxjsを使うメリット

operatorの数が多い
処理のフローが分かりやすい
データを自由に加工できる

Example

App.js
import React, { useState, useEffect } from 'react';
import { from  } from 'rxjs';
import { map, filter, mergeMap, delay } from 'rxjs/operators';

// observableを作成
let numberObservable = from([1, 2, 3, 4, 5]);

// operatorでobservableからのデータを操作
let squareNumbers = numberObservable.pipe(
    filter(val => val > 2),
    mergeMap(val => from([val]).pipe(
        delay(1000 * val)
    )),
    map(val => val * val)
);

// observableフックの作成。
// unsubscribeしなければデータストリームが止まらない限り実行され続ける
const useObservable = (observable, setter) => {
    useEffect(() => {
        let subscription = observable.subscribe(result => {
            setter(result);
        });

        return () => subscription.unsubscribe();
    }, [observable, setter]) 
}

const App = () => {
    const [currentNumber, setCurrentNumber] = useState(0);

    useObservable(squareNumbers, setCurrentNumber);

    return (
        <div className="app">
            Current number is: {currentNumber}
        </div>
    );
}

export default App;

おわりに

誤字、誤った説明があった場合、お手数ですがコメント等下さい。

参考

https://www.youtube.com/watch?v=KOjC3RhwKU4&t
https://www.youtube.com/watch?v=49dMGC1hM1o&t
https://www.youtube.com/watch?v=uQ1zhJHclvs&t
https://www.youtube.com/watch?v=eloMMybBVN0
https://www.youtube.com/watch?v=Tux1nhBPl_w
https://www.youtube.com/watch?v=GCPORlQDFHI
https://www.youtube.com/watch?v=Urv82SGIu_0&t

15
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
9