なぽろぐ

気ままに感じたことを記事にまとめます。

rxjs 用の suspense ライブラリを作った

Observable から suspense でデータを取得するライブラリ作った

firestore や websocket など変化したデータを watch することができる際に有効になりそうなライブラリです。

なにができるか

rxjs で作られる Observable を subscribe すると流れてくる1つ目の data を suspense で取得し、その後も流れてくるデータも反映してくれるものです。 ライブラリが提供している datasouce という関数は observable を作る関数を引数に取るので filter 条件や、id、画像の small, medium, large などを後刺しできます。

firestore v9 でこのライブラリを使用する際は次のようになります。

import { Observable } from "rxjs";
import { getFirestore, collection, onSnapshot } from "firebase/firestore";
import { datasource, useDataRead } from "@naporin0624/react-flowder";

type Post = {
  id: string;
  content: string;
  createdAt: number;
}
type Filter = {
  // filter condition params
}
const db = getFirestore();
const postsCollection = collection(db(), "posts");
const posts = (filter: Filter) => new Observable<Post[]>((subscriber) => {
  // filter params を使って query の変更などを行う
  return onSnapshot(ref, (snapshot) => {
    const posts = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
    subscriber.next(posts);
  },
  (err) => subscriber.error(err),
  () => subscriber.complete(),
);

const postsDatasource = datasource((filter: Filter) => posts(filter));

const App = () => {
  const filter = useMemo<Filter>(() => ({ }), []);
  const data = useDataRead(postsDatasource(filter));

  return <p>{JSON.stringify(data, null, 2)}</p>
}

提供してる関数

datasource

github.com

Observable を生成する関数を引数にとって datasource として登録する関数です。 内部でキャッシュと対応付けるための unique な string の key を生成します。

Provider

github.com

内部的に持っているキャッシュの保持を行います。 このライブラリは僕が作っているライブラリを2つ掛け合わせて構成されているので、キャッシュの同期機構が Provider には書かれています。

useReadData

github.com

useReadData(datasource()) という形で使われ、データの Suspense による取得とデータの購読を行います。

useReset

github.com

useReset() or useReset(datasource), useReset(datasource(xxx)) という形で使われます。

useReset は関数を返し、 その関数を実行するとキャッシュが破棄され再度 Suspense されます。

引数に datasource を入れた時に破棄されるキャッシュは datasource を引数にとって得られたものだけに限定されます。

usePrefetch

github.com

useReadData を使用せずともデータを取得してキャッシュに入れたいときに使用します。loading を state として持ちたいときに便利です。

startTransition が来るまではこれで代用できるはず。

const prefetch = usePrefetch(datasource);
const onClick = async () => {
  await prefetch(args);
}

最後に

rxjs と react が非常に親和性が高いと思い込んだ結果できたライブラリです。半年くらい使用してみた結果かなり使えることが分かったのでブログを書いてみました。特に firestore との親和性が高いです。Suspense の欠点であるデータの取得が直列になってしまい、初回のレンダリングに時間がかかるという部分に関してはまだ対応できてないですが、そこにも手を入れたいと思っています。useReadDataAll 的な hook ができるはず。ぜひ、rxjs で Suspense を体験してみたいという方に使ってみてほしいです。よろしくお願いします。

内部実装では useEffect, useState を使って記述していた部分を use-sync-external-store に置き換えてみました。