Reduxの代わりにFirebaseを使えるか?

記事
IT・テクノロジー

Reduxの代わりにFirebaseを使えるか?

ReactでWebアプリやWebサービスを開発する場合、少し複雑なものになると色々な値の受け渡しも複雑になります。 そこで、データをまとめて管理するために、Reduxなどを利用する場合も多くなります。一方で、Reduxは慣れないと少しわかりにくい部分も多く、初心者にはハードルが高い場合もたくさんあります。
この記事では、FirebaseのデータベースをReduxの代わりに利用する可能性について考えてみました。

意外に高速なFirebaseのデータベースアクセス

Reduxの代わりにFirebaseのデータベースにデータを置いて、データを一元管理することは技術的には可能です。当然、アクセスするスピードはReduxに置いた場合の方が高速なのは言うまでもありません。Firebaseのデータは基本的にFirebaseのサーバーに置かれていますのでデータの取得にはネットワークアクセスが必要です。当然、メモリにあるデータ(=Reduxのデータ)の方が高速でアクセスできます。
しかし、Freibaseのデータベースアクセスは意外に高速です。以前、SendGridというE-Mailベースのメッセージ配信サービスのバックエンドの開発を受けて行いました。この時、最初の実装はフロントエンドからのリクエストをSendGridのサーバーに転送して、その応答をフロントエンドに返すという、仲介役の様な実装をしていました。しかし、実装を少し工夫して、FirebaseにSendGridの情報をコピーしておいてFirebaseを介してデータを取得するように変更したところ、ページの読み込み時間が大きく改善しました。
具体的な例は、SendGridでメッセージ送信の配布先のE-Mailアドレスのリストを取得したい場合の処理です。
元々の実装は、
1.フロントエンドがリスト取得のリクエストをバックエンドに送信
2.バックエンドの処理はリクエストをSendGridに転送
3.SendGridのサーバーがリクエストされたリストをバックエンドに応答
4.バックエンドの処理はこのリストをリクエスト元のフロントエンドに応答として返す
という流れで処理されていました。
これを以下のように変更しました。 0. フロントエンドは初期化でFirebaseにおいてあるリストを「onSnapshot()」で取得
1.フロントエンドはバックエンドのデータの更新をリクエスト
2.バックエンドはリクエストを受け取ったら応答を返信、同時にSendGridにリストを要求
3.SendGridのサーバーが応答データとしてリストをバックエンドに返信
4.バックエンドはFirebaseのデータベースを更新
5.Firebaseのデータベースが更新されると、自動的にフロントエンドのデータも更新
のように変更したら殆ど待ち時間なしで処理できるようになりました。
改善の理由はFirebaseのアクセスが高速である以外の理由があります。データを先に読み込んでいるので待ち時間が改善している部分は少なくありません。しかし、単純にデータを受け取るまでのレスポンスタイムを考えても、SendGridからデータを取得するよりは高速で処理ができます。

Firebaseのリアルタイムのモニタ機能をうまく使う

上の例ではデータベースの「onSnapshot()」の機能を利用して、データ更新のイベントを検出してデータを更新しています。同じような機能に「onAuthStateChanged()」を使えばログイン状態の更新も検出可能です。これを、Reactの「ステート(state)」と上手く合わせて使えば、Reduxに似た機能を実現できます。
Firebaseのデータベースに基本的なデータを置いて、実際に使用するデータだけをステートに取り込んで利用するという方法です。データが変更されれば、「onSnapshot()」の機能を使えば、ステートも更新されるので、表示しているデータの更新も行うことが可能です。ページが変わってもFirebaseからデータを取得すれば、Reactの機能を通じて値を受け渡しする必要もなくなります。
同じようにログインの状態も直接Firebaseから取得可能で、ログイン状態の変化も検出できるので殆どの処理はReduxのように使うことは可能です。

注意することは?

ではReduxは不要かというと、Reduxを利用する目的をよく考える必要はあります。
* Reduxの方が高速アクセスが可能 
* データ更新の履歴を追跡可能
* データのアクセスは同期アクセス可能(Firebaseは非同期アクセス)
など違いはあります。データのアクセスは少なくても読み込みは同期でデータを取得できます。 データの更新は、厳密にいうと同期で更新されない場合もありますが、読み込みは同期で可能です。
WebアプリやWebサービスの仕様によっては、Reduxを使わなければいけない場合はあると思います。 しかし、かなり多くのケースではFirebaseで代用しても問題ないケースも確実にあります。
Firebaseを使った場合は、Reduxに比べればコードもシンプルになります。 実際の要求に合わせてFirebaseを利用する方法も十分検討の余地はあるかと思います。

実装例は?

例えば、お問合せフォームやブログ投稿の管理ページの実装を考えます。
お問合せフォームのお問い合わせのリストのアクセスや、ブログの記事投稿は一般の利用者とは別の権限を設定する必要があります。これは、別の記事でも説明した通りです。
そこで、管理者を設定する必要があります。管理者を識別するために特別なE-Mailアドレスでのログインが必要になりますよね?
そこで、ログイン状態を監視するコードを実装します
import firebase from "../lib/firebase";
import LoginForm from "../components/loginform";
import DisplayData from "../components/displaydata";
interface IState {
    signin:boolean;
    data:Array<firebase.firestore.DocumentData>;
}
interface IProps {}
const ADMIN_USER:string = "UID-XXXXXXXXX";
class Admin extends React.Component<IProps, IState> {
    // onSnapshot() をキャンセルするための関数
    private unsubscribe:(()=>void)|undefined;
    // コンストラクタ
    constructor(props:any) {
        super(props);
        this.state = {
            signin:false,
            data:[]
        }
        this.init();
    }
    init() {
        firebase.auth().onAuthStateChanged((user:firebase.User | null) => {
            if (user && uid === ADMIN_USER) {
                // ログイン状態 (管理者』
                //
                // データを取得
                this.getData();
                this.setState({
                    signin:true;
                });
            } else if (user && uid !== ADMIN_USER) {
                // ログイン状態 (管理者以外)
                //
                // onSnapshot() のモニターを停止
                if (this.unsubscribe) {
                    this.unsubscribe();
                }
                firebase.auth().signOut().then(() => {
                    this.setState({
                        signin:false,
                        data:[], // データを開放
                    });
                })
            } else {
                // onSnapshot() のモニターを停止
                if (this.unsubscribe) {
                    this.unsubscribe();
                }
                // ログアウト状態
                this.setState({
                    signin:false,
                    data:[], // データを開放
                });
            }
        })
    }
    /** Firebase Cloud Firestore からデータを取得(データの更新をモニタ) */
    getData() {
        // データの更新をモニタ
        this.unsubscribe = firebase.
            firestore().
            collection().
            onShanpshot((querySnapshot:firebase.firestore.QuerySnapshot) => {
                const data:Array<firebase.firestore.DocumentData> = [];
                for (let i = 0 ; i < querySnapshot.size ; i++ ) {
                    data.push(querySnapshot.docs[i])
                }
                // データを更新
                this.setState({
                    data: data
                })
        })
    }
    render() {
        if (this.state.signin) {
            // データを表示
            return(<DisplayData/>);
        } else {
            // ログインフォームを表示
            return(<LoginForm/>);
        }
    }
}
管理者でログインしている場合は、データを取得して表示します。 管理者以外でログインしている場合は直ちにサインアウトして、ログイン画面を表示します。 ログインしていない場合も、ログイン画面を表示します。
ページごとにステートを管理する必要はありますが、Reduxは必要ありません。 別のページで同じデータが必要な場合は同じように、データを読み込めば対応可能です。また、Firebaseのデータが更新された場合にも対応できます。「onSnapshot()」で更新イベントを検出しているので、別のページでデータが更新された場合でも、ここで保持しているデータは更新されます。
複数のページで同じデータを扱う場合も、Firebaseからデータを取得すればReact内でデータを受け渡しする必要はありません。

まとめ

Firebaseの機能をうまく使うと、Reduxを使わないでもWebサービスやWebアプリの実装ができます。
Reduxを使った方が良いケースも当然沢山あると思います。しかし、一部の実装ではReduxを使わずに必要な機能を実装できる事例もたくさんあります。特にシンプルなコードで実装したい場合、Firebaseの機能を利用した方がシンプルになるケースも多く、初心者でも少し複雑なWebアプリやWebサービスの実装が簡単にできる場合もたくさんあります。
しかし、欠点があるのも事実でFirebaseで完全にReduxを置き換えられるということではありません。
アクセスは当然メモリ上にデータを保持しているReduxの方が高速です
Reduxは簡単にデータの更新履歴をトラックできます。
場合によっては、Reduxよりメモリの使用量が増えてしまうことがあります(メモリ管理の実装次第)
などは、Firebaseでの実装を検討する場合考慮する必要があります。
サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す