Closed7

RxJSの勉強

Yuta OhiraYuta Ohira

クリックイベントを取得する

https://www.learnrxjs.io/learn-rxjs/operators/creation/fromevent

rxjsを使わないパターン

const appEl = document.getElementById("app")!;

appEl.addEventListener("click", () => {
  console.log("clickイベント発火");
});

rxjsを使ったパターン

import { fromEvent } from "rxjs";

const appEl = document.getElementById("app")!;

const clickObservable$ = fromEvent(appEl, "click");
clickObservable$.subscribe(() => console.log("clickイベント発火"));
Yuta OhiraYuta Ohira

Observableとは?

streamで流れてきたデータを、データ加工や間引き処理などの処理を行う部分。
e.g. 水道(stream)に水(データ・イベント)を流し、蛇口(Observable)でろ過処理(フィルタリング)を行う。

Observableは、nexterrorcompletedの3つの処理を持つ。

https://rxjs.dev/guide/observable
https://www.learnrxjs.io/learn-rxjs/operators/creation

Observableの例

// 1,2,3,(1秒待機),4,(完了)
const observable$ = new Observable(s => {
  s.next(1);
  s.next(2);
  s.next(3);
  setTimeout(() => {
    s.next(4);
    s.complete();
  }, 1000);
});
observable$.subscribe(console.log);
// appEl.addEventListener("click", console.log);
const observable$ = fromEvent(appEl, "click");
observable$.subscribe(console.log);
// window.addEventListener("resize", console.log);
const observable$ = fromEvent(window, "resize");
observable$.subscribe(console.log);
// let count = 0;
// setInterval(() => {
//   console.log(count);
//   count++;
// }, 1000);

const observable$ = interval(1000);
observable$.subscribe(console.log);
Yuta OhiraYuta Ohira

pipeについて

Observableから新たなObservableを作成する。pipeの引数には、複数のオペレータを指定できる。

https://rxjs.dev/guide/operators

例: windowのリサイズを検知

const onResizeWindow$: Observable<Event> = fromEvent(window, "resize");
onResizeWindow$.subscribe((event: Event) => console.log(event));

上記のEventを流すObservableから、画面サイズを返すObservableを作成する。

type WindowSize = {
  width: number;
  height: number;
};

const onResizeWindowSize$ = onResizeWindow$.pipe((obs) => {
  return new Observable<WindowSize>((s) => {
    obs.subscribe((event) => {
      s.next({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    });
  });
});
onResizeWindowWidth$.subscribe((size: WindowSize) => console.log(size));

mapというオペレータを使うと、もっと簡単に書ける。

const onResizeWindowSize$ = onResizeWindow$.pipe<WindowSize>(
  map((event) => {
    return {
      width: window.innerWidth,
      height: window.innerHeight
    };
  })
);
Yuta OhiraYuta Ohira

throttle と debounce について

throttleとは、連続して大量に繰り返される処理を一定間隔で間引くこと。
debounceとは、連続して大量に繰り返される処理が指定時間内に何度発生しても、最後の一回だけを実行すること。

https://blog.n-t.jp/post/tech/difference-throttle-debounce/

windowのリサイズの例

windowのリサイズは、連続して大量に繰り返されるので、throttledebounceを用いて、いい感じに調整する。

デバッグ用にロギング関数を用意

const logger = (data: any) => {
  const now = new Date();
  const timeLabel = `${now.getSeconds()}.${now.getMilliseconds()}s`;
  console.log(timeLabel, data);
};

素の処理

const onResizeWindowSize$ = onResizeWindow$.pipe<WindowSize>(
  map((event) => {
    return {
      width: window.innerWidth,
      height: window.innerHeight
    };
  })
);
onResizeWindowSize$.subscribe(logger);

画面サイズが変化している途中でも、ログが出力される。

throttle(500ms)

https://rxjs.dev/api/index/function/throttleTime

const onResizeWindowSizeWithThrottle$ = onResizeWindowSize$.pipe(
  throttleTime(500)
);
onResizeWindowSizeWithThrottle$.subscribe(logger);

画面サイズが変化し続けても、500ms間隔でしかログが出力されない。

debounce(500ms)

https://rxjs.dev/api/operators/debounceTime

const onResizeWindowSizeWithDebounce$ = onResizeWindowSize$.pipe(
  debounceTime(500)
);
onResizeWindowSizeWithDebounce$.subscribe(logger);

500ms間、画面サイズが変化しない状態が続くと、今の画面サイズがログとして出力される。

Yuta OhiraYuta Ohira

ResizeObserverで画面サイズを監視

resizeイベントは、windowでのみ提供されているため、特定のelementのサイズを監視するためには、ResizeObserverを使用する必要がある。

https://developer.mozilla.org/ja/docs/Web/API/ResizeObserver

// windowを監視
window.addEventListener("resize", () => {
  const size = {
    width: window.innerWidth,
    height: window.innerHeight
  };
  console.log(size);
});

// id="app"のelementを監視
const observer = new ResizeObserver((entries) => {
  const { width, height } = entries[0].contentRect;
  const size = { width, height };
  console.log(size);
});
const appEl = document.getElementById("app")!;
observer.observe(appEl);

上記の処理をRxJSに書き換える

const createResizeObservable = (el: Element) => {
  return new Observable<WindowSize>((s) => {
    const observer = new ResizeObserver((entries) => {
      const { width, height } = entries[0].contentRect;
      s.next({ width, height });
    });
    observer.observe(el);
  }).pipe(debounceTime(500));
};

const appEl = document.getElementById("app")!;
const onResizeAppSize$ = createResizeObservable(appEl);
onResizeAppSize$.subscribe(console.log);
Yuta OhiraYuta Ohira

avaでRxJSのテストを書く

https://github.com/avajs/ava

テスト対象の関数を実装する

何らかのデータ・イベントが流れたときに、まずtrueを返し、x秒後にfalseを返すObservableを生成する関数createToggleFlagObservableを実装する

import { delay, map, merge, Observable } from "rxjs";

export const createToggleFlagObservable = (
  onEvent$: Observable<unknown>,
  delayTime: number = 1000
) => {
  const enableFlag$ = onEvent$.pipe(map(() => true));
  const disableFlag$ = onEvent$.pipe(delay(delayTime)).pipe(map(() => false));
  const toggleFlag$ = merge(enableFlag$, disableFlag$);
  return toggleFlag$;
};

ユースケース

特定のエリア(element)をクリックしたときに、ポップアップを表示し、5秒後にポップアップを非表示にする。

const appEl = document.getElementById("app")!;
const clickAppEl$ = fromEvent(appEl, "click");
const toggleFlag$ = createToggleFlagObservable(clickAppEl$, 5000);
toggleFlag$.subscribe((flag) => {
  if (flag) {
    console.log("ポップアップを表示");
  } else {
    console.log("ポップアップを非表示");
  }
});

テストを実装

import { TestScheduler } from "rxjs/testing";
import test from "ava";

test("クリックした後にポップアップを表示し、2秒後にポップアップを非表示", (t) => {
  const scheduler = new TestScheduler(t.deepEqual.bind(t));
  scheduler.run(({ hot, expectObservable }) => {
    const onEvent$ = hot("a", { a: undefined });
    const toggleFlag$ = createToggleFlagObservable({
      onEvent$,
      delayTime: 2000
    });
    expectObservable(toggleFlag$).toBe("a 1999ms b", {
      a: true,
      b: false
    });
  });
});
このスクラップは2022/06/07にクローズされました