webアプリでfirestoreを使ってみる(react.js)

はじめまして。
QBオンライン内製チーム、フロントエンド担当のchikamaです。

QBオンラインとは直接関係ないですが、
react.jsを使ったwebアプリでfirebase firestoreを
使ってみたのでその備忘録を簡単に書きたいと思います。

今回はreact.js+webpackでwebアプリ(SPA)を作成しているため、
npmからfirebaseを読み込む形で利用しています。


事前準備

root/
 ├ firebase/
 │ ├ config.js
 │ └ index.js
 │   
 └ reactjsファイル群

まずは設定ファイル。firebaseフォルダ以下に2つのファイルを配置します。
フォルダ構成に関しては説明外の部分は省略しています。

・config.js

export const config = {
   apiKey: "*******",
   authDomain: "*******.firebaseapp.com", 
   databaseURL: "https://*******.firebaseio.com",
   projectId: "*******",
   storageBucket: "*******.appspot.com",
   messagingSenderId: "*******"
 };

・index.js

import firebase from 'firebase';
import {config} from './config.js';

export firebaseApp = firebase.initializeApp(config);

config.jsにはfirebaseコンソール上から拾える情報をまとめて、
index.jsで初期化を済ませてreact.js上でimportするだけで利用できる形にしました。


実際に処理を書いてみた

イベントをトリガーとしてfirestoreにあるドキュメントの取得と、
リスナーを利用したリアルタイムアップデートを実装してみました。

import {firebaseApp} from '../firebase';

const firestore = firebaseApp.firestore();

react.js側で利用するときはimportで呼び出し、
const firestore = firebaseApp.firestore();としておきます。
こうするとfirestoreという定数から各メソッドを使えるようになります。

・単一ドキュメントの取得

handleSample = () => {
     if(this.isEmptyObject(this.state.data)) {
               let select = await this.loadFirestoreData();
               this.setState({
                         data: select,
               });
     }
};

loadFirestoreData = () => {
    return new Promise((resolve) => {
        let key = this.state.key;
        let select = {};
        firestore.collection(collectionKey)
            .doc(docKey)
            .get().then(result => {
            let res = this.objectSort(result.data());
            Object.keys(res).map(docs => {
                select[res[docs].id] = {'id': res[docs].id, 'no': res[docs].no};
            })
            resolve(select);
        })
    })
}

メニューを開いた処理をトリガーとして、そこに表示するデータを取ってきています。
今回の場合、単一ドキュメントの中に複数のmap要素を格納しているため、keyを指定して全取得させています。
componentDidmountなどで初期表示データとして取得するときも使えます。

・リアルタイムアップデート

onLoadSnapShot = (id, no) => {

   this.unsubscribe = fireStore.collection(collectionKey)
       .doc(id)
       .collection(no)
       .orderBy("id","desc")
       .onSnapshot(snapShot => {
           let sample = [];

           snapShot.forEach(function(res) {
               sample.push(res.data());
           })

           this.setState({
               list:sample,
           })
       })
}

componentDidMount = () => {
   this.onLoadSnapShot(this.props.id, this.props.no);
}

componentDidUpdate = (prevProps, prevState) => {
   this.unsubscribe();
   this.onLoadSnapShot(this.props.id, this.props.no));
}

チャットのように複数人が同時にデータを更新する前提で書いています。
送信されているチャット内容を.onSnapShotというリアルタイムアップデートのメソッドを利用して表示しています。
リスナーをデタッチするまでの間、対象のドキュメントが更新されるたびに呼び出しが発生し、
新しいドキュメント内容がsetStateされることになります。
こうした実装方法によって誰かが書き込むたびに画面が再描画されるように実装できました。
(リアルタイムアップデートの公式ドキュメント

・失敗した点

最初、onSnapShotの外に変数の定義をしていたらドキュメントが更新されるたびに表示内容が倍々以上に膨れ上がって更新されてしまいました。
内部の変数の定義は維持され続けるということが理解できてなかったために初期化の処理ができておらず、今ある表示に全表示+新規投稿を追加するという状態になってしまっていました。
処置としてメソッドの内部に変数を定義しましたが、良く考えたら中身をクリアした方が良かったですね。


まとめ・所感

web上で利用を開始するうえで、SDKを読み込む方法が公式ドキュメントで3つほどありましたが
個人的にはnpmモジュールを使った方法が開発時に一番シンプルに利用できて楽でした。

今回NoSQLのデータベースと初めてしっかりと触ったのですが、
最初にしっかり設計をしておかないと後から既存データも含めて要素を追加しようとしたときにスクリプト組んだりして結構つらかったです。
初期段階の設計がどれだけ大事か改めて痛感しました。。。
スクリプトで既存データをぐるぐる回す以外にいい方法ってあるんでしょうか。

リアルタイムアップデートが簡単に実装できたのがかなり魅力的でした。
DBのデータが変更されるのを感知する仕組みが簡単に実装できるので他に応用しやすいですよね。
他のSPAでも、変更検知→画面再描画の流れは利用できると思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Bitnami