Firebase Web クライアントのイベントを処理する(RxFire, ReactFire)

本記事では Firebase Web クライアントの通知を処理しやすくなるかもしれない RxFire と ReactFile をみていきます。

【目次】

[1]はじめに

私が Firebase は面白いと感じることの一つに、様々な通知が得られることがあります。

例えば、Firebase Authentication であれば、ログイン、ログアウト状態の変化の通知を受け取ることができます。Firestore であれば、データベースの変更等の通知を受けることができます。

このような通知をうまく利用すれば、面白い動きをするアプリが作れそうな気になります。

一方で、アプリの規模が大きくなるにつれ、ユーザからのアクションとシステム等からの通知に対して一貫性を保って動作する設計や実装を行うことは難しいものです。

そこで本記事では手始めとして、Firebase Web クライアントの通知を処理しやすくなるかもしれない枠組みやライブラリを少しだけ見ておきます。

[2]Firebase Web SDK をそのまま使う

Firebase クライアントライブラリから受けられる通知は沢山ありますが、ここではよくありそうな例を3つだけみておきます。

(1)Authentication 通知例

以下のようなコードで、ユーザのログイン状態(ログインした、ログアウトした)の変化の通知を受けることができます。

import { getAuth, onAuthStateChanged } from "firebase/auth";

const auth = getAuth();
const unsubscribe = onAuthStateChanged(auth, (user) => {
  if (user) {
    // ユーザがログインしている場合は User オブジェクトが得られる。
    // ...
  } else {
    // それ以外はログインしていない(ログアウトした等)
  }
});

// 通知不要になったとき
// unsubscribe();

具体的な説明やコードサンプルは以下を参照して下さい。

(2)Firestore 通知例

Firestore の面白いところは、データベースの変化を onSnapshot で通知を受けられるところだと思います。

具体的な説明やコードサンプルは以下を参照して下さい。

(3)Storage 通知例

Cloud Storage へファイルをアップロードするとき、uploadBytesResumable を利用すれば、アップロードの進捗(転送済みのバイト数など)の通知を受けることができます。

具体的な説明やコードサンプルは以下を参照して下さい。

[3]RxFire を利用する

(1)RxJS と RxFire

認証状態やデータベースの変更などのイベント通知は継続的に発生します。継続的に発生するイベントは Rx を利用すると、高度なイベント処理が実装しやすくなる可能性があります。

ここでは Rx の説明は割愛しますが、Rx の基本概念の一つは Observable です。

Firebase が発行する各種イベントを Rx の Observable として提供してくれるライブラリが RxFire です。

RxFire を利用すると、Rx ライブラリが提供するオペレータを利用して複雑なイベント処理を比較的シンプルに記述できる可能性があります。


ところで、RxFire は Firebase の SDK を包含するようなフレームワーク的なものではなく、Firebase が提供する API に Rx を利用するための機能追加するライブラリです。よって Firebase の API と併せて利用する必要があります。

(2)RxFire のインストール

RxFire をインストールする際は、Firebase と RxJS のインストールが必要です。
# npm
npm i rxfire firebase rxjs --save
# yarn
yarn add rxfire firebase rxjs

package.json をみると、Firebase SDK のバージョン 9 以上に対応しているようです。

(参考)Vite + React + TypeScript でプロジェクトを構成する場合の注意事項

Vite + React + TypeScript でプロジェクトを構成する場合、インストール手順は上記で問題ありませんが、(この記事を書いている時点では)インポートするときに以下のようなエラーが出ます。

'XXX' is declared but its value is never read.ts(6133)
Could not find a declaration file for module 'rxfire/YYY'. ZZZ implicitly has an 'any' type.

There are types at '...', but this result could not be resolved when respecting package.json "exports". The 'rxfire' library may need to update its package.json or typings.ts(7016)

この問題への対処方法をネットで調べてみたところ、現状最も簡単な解決策は、下記設定を tsconfig.json の compilerOptions に入れることのようです。

{
 "compilerOptions": {
    "resolvePackageJsonExports": false,
 },
 ...
}

(3)RxFire の利用例

RxFire が提供するAPI仕様とサンプルコードは以下にあります。

ただ、ざっと見ただけだと、コールバックが subscribe に変わるだけじゃないか?と思われるかもしれません。

実際に、pipe 等で情報を加工するようなシナリオがないと、コールバックで十分な場合も多く、アプリの設計として Rx を利用しているようなケース以外はオーバースペックに感じるかもしれません。

一方、個人的に面白く感じたのは、「Introducing RxFire: Easy async Firebase for all frameworks」の記事にある利用シナリオです。

例えば以下のコードは、Firestore で2つのデータソースから取得したデータを Rx の combineLatest を用いて結合する例です。更新タイミングが異なる複数のデータを簡単に結合できます。確かにこれは便利です。

これをみると部分的にでも RxFire(RxJS)を利用する価値があると思えました。

import firebase from 'firebase/app';
import 'firebase/firestore';
import { collectionData, docData } from 'rxfire/firestore';
import { combineLatest } from 'rxjs';

const app = firebase.initializeApp({ /* config */ });
// Create observables of document and collection data
const profile$ = docData(app.firestore().doc('users/david'));
const cart$ = collectionData(app.firestore().collection('carts/david'));
const subscription = combineLatest(profile$, cart$, (profile, cart) => {
  // transform the profile to add the cart as a child property
  profile.cart = cart;
  return profile;
})
  .subscribe(profile => { console.log('joined data', profile); });

// Unsubscribe to both collections in one call!
subscription.unsubscribe(); 

[4]ReactFire を利用する

(1)ReactFire

RxFire は特定の UI Framework に依存することなく利用できます。(そのように設計されています)

一方で、Angular、Vue、React 等のフレームワークを利用する場合は、フレームワークから利用しやすい何かが欲しくなります。

例えば、React を利用する場合なら、イベント通知を受けてレンダリングするためのフックが欲しくなります。

ここでは RxFire を利用して React のフックを提供するライブラリ ReactFire をみておきます。

ちなみに、ReactFire は Google 公式ライブラリではないようですが、Googlerにより作られていると書かれてます。


(2)ReactFire のインストール

以下のコマンドでインストールできます。

# npm
npm install --save firebase reactfire
# yarn
yarn add firebase reactfire

ところで、ReactFire は(この記事を書いている時点では)Firebase SDK のバージョンに依存するので注意が必要です。

具体的には、ReactFire の package.json には「firebase": "^9.23.0"」と書かれているため、先に Firebase の最新バージョン(10.7.0)を入れてから ReactFire をインストールしようとすると失敗します。
このため、上記のように firebase と reactfire を一緒にインストールするのが無難です。(逆に言えば、最新の Firebase SDK を利用する必要がある場合に、そのままでは使えない。。。)

なお、RxFire は明示的にインストールしなくても依存関係からインストールされます。但し、RxFire を Vite+React+TypeScript で利用する場合は、上記注意事項を参考にしてください。

(3)ReactFire の利用例

ReactFire は、RxFire と違って、Firebase SDK の内容を React の流儀に従ってある程度包含しているライブラリになっています。

例えば、initializeApp のような初期化APIは、FirestoreProvider でラップされており、コンポーネント内から ReactFire が提供するフックを利用すると内部解決されるようになっています。

主な利用ケースは以下が参考になります。

例えば、useSigninCheck フックを利用するとログイン状態に応じたレンダリングが簡単にできます。

export const Auth = () => {
  const { status, data: signinResult } = useSigninCheck();

  if (status === 'loading') {
    return <LoadingSpinner />;
  }

  const { signedIn, user } = signinResult;

  if (signedIn === true) {
    return <UserDetails user={user} />;
  } else {
    return <SignInForm />;
  }
};

ReactFire が提供するフックが利用用途にあえば非常に便利に使えると思います。また、RxFire や RxJS は内部に隠蔽されているので、これらの知識は無くても利用できそうです。

しかし、このことは RxJS の機能を利用した拡張を行いたい場合はこれがネックになります。

それでも RxFire の利用例として ReactFire のコードを見る価値はあると思います。

何れにしても、React ではレンダーとの関係から、可能であれば安定したライブラリを使いたいところです。もし Rx を利用しないのであれば、他にも Firebase 関係のフックを提供してくれるライブラリがいろいろあるようです。

コメント

このブログの人気の投稿

Google Document AIで画像から表形式データを抽出する(Vision API OCRとの違い)

VirtualBoxのスナップショット機能

オントロジーエディタ Protégé を使ってみる