Initial Site

Initial Site

Initial Site

ReactとFirebase(Cloud Firestore)を使って独り言WEBアプリをつくる

  • ヨシモト  2020/02/21 19:10
complete

開発部 ヨシモトです。
 
今回はReactとFirebaseを使ってツイッター風の独り言WEBアプリを構築して解説したいと思います。
 

 

完成図

complete

目次へ戻る

 

対象者

  • ・Reactをこれから触ってみようと思っていて、とりあえず公式のチュートリアルはやってみたという人
  • ・Firebase(特にCloud Firestore)をこれから触ってみようと思っていて、とりあえず公式のクイックスタートはやってみたという人
  • ・bash等のコマンドラインでの簡単な操作ができる人(ls, cd, touchくらいできたら大丈夫と思います)

 

目次へ戻る

 

必要なもの

  • ・ブラウザ(Chromeを推奨しますが、IEでなければなんでもいいと思います。)
  • ・テキストエディタ(VSCodeを推奨しますが、なんでもいいです)

※ローカルで動くサーバがある方は↑だけでokですが、ない方は↓を用意しましょう。

  • ・ターミナルエミュレータ(VSCodeならテキストエディタもターミナルエミュレータあるので楽です、なければなんでも)
  • ・Node.js(バージョン指定はしませんがv12.16.1で動かしています)

※OSはMac, Windows, Linuxならなんでもいいです。
ただし、WindowsならコマンドプロンプトやPowerShellでなくて、WSLかGit Bashを推奨します。

 

※Reactなどはインストールしても良いのですが、今回はなるべくお手軽にしたいので可能な限りCDNを使います。

目次へ戻る

 

要件

  • ・ログイン機能なし(公開前提ならばつけましょう)
  • ・ツイート(ポスト)はCloud Firestoreへ保存
  • ・コンテンツ部分(ポスト入力、 ポスト表示)はReactで書く
  • ・CSSはMaterializeCSSにおまかせ
  • ・ローカルでサーバを立ち上げて動作させます

 

目次へ戻る

 

ファイル構成

app/ T index.html
     ∟ app.jsx

 

 

目次へ戻る

 

実践

1. htmlファイルを作る

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ひとりごと</title>

    <!-- style読み込み -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
    <!-- ヘッダー -->
    <nav class="nav-wrapper">
        <div class="container">
            <a class="brand-logo" href="./index.html">
                ひとりごと
            </a>
        </div>
    </nav>

    <!-- ここにコンテンツが入ります -->
    <div id="app"></div>

    <!-- フッター -->
    <footer class="page-footer">
        <div class="container">
          <div class="row">
            <div class="col l6 s12">
              <h5 class="white-text">ひとりごと</h5>
              <p class="grey-text text-lighten-4">ひとりごとを呟きます</p>
            </div>
          </div>
        </div>
        <div class="footer-copyright">
          <div class="container">
          © 2020 InitialSite yoshimoto.
          </div>
        </div>
      </footer>
    <!-- materialize -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>

    <!-- firebase -->
    <script src="https://www.gstatic.com/firebasejs/7.7.0/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/7.7.0/firebase-firestore.js"></script>
    
    <!-- react -->
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

    <!-- babel(reactのjsxをjsに変換してくれるものです) -->
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

    <!-- 作成するjsファイル -->
    <script src="./app.jsx" type="text/babel"></script>
</body>
</html>

入力部や投稿表示部はReactで行うので、このファイルではヘッダー、フッター、CDNの読み込みのみにします。

目次へ戻る

 

2. ローカルサーバを動かす

ローカルで動かせるapacheやNginxがあればそれでもokですし、
Pythonのhttp.serverやPHPのビルトインサーバでもokです。

 

※ローカルで動くサーバがない人は下記を行いましょう。

1. ディレクトリを作る
コマンドライン
$ mkdir app
コマンドライン
$ cd app
2. expressをインストールする
コマンドライン
$ npm i express
3. server.jsを作る
コマンドライン
$ touch server.js
server.js
'use strict';
const express = require('express');
const server = express();

server.use(express.static('./'));
server.listen(5000, () => console.log('app listening on port 5000.\n\n http://localhost:5000')); 
4. server.jsを動かしてみる
コマンドライン
$ node server.js

コンソールにlocalhostへのリンクが出ますのでクリックして表示を確認しましょう。

こうなっているはずです。

pre-top

目次へ戻る

 

3. Firebaseの設定をする

1. Firebaseのページへ行き、プロジェクトを作成する

createProject

2. 名前を付けたりします。(アナリティクスは無効でいいと思います)

name

 

3.  プロジェクトが作成できたらコンソールトップからDatabaseへ

top

 

4.  データベースの作成をする

db

 

5.  今回はテストモードで作ります。

後述しますが、誰でも読み書きできるようになっています。

notice

 

6.  ロケーションはasia-northeast1にします

これが恐らく東京かと思われます。(asia-northeast2は大阪でしょうか?)未検証。

一応、どこにしても動きますが、近い場所の方が速いはずです。(こちらも未検証)

location

 

7. データベースが作成できたらProject Overvireへ戻ります。

dbTop

 

8.  WEBアプリFirebaseを利用するにはこちら

top2

 

9. アプリ名などを入力します。今回はFirebase Hostingを利用しません

addWeb

 

10.  ここで表示されるkeyなどのconfig情報はこのあと使いますのですぐにコピペできるようにしておきましょう。

ちなみに、このスクリーンショットではconfigを隠しておりますが、こちらも後述するルール設定を適切に行えば隠さなくても大丈夫です。

conf

 

目次へ戻る

 

4. app.jsxを作る

4-1. Appクラスとrender
app.jsx

'use strict';

class App extends React.Component {
  render() {
    return (
      <div className="row" style={{margin: "1em"}}>
        <div className="col s12 m5 l5">
          <form style={{marginTop: "4em"}}>
              <textarea
                type="text"
                id="text"
                className="materialize-textarea col s9"
              />
              <button type="submit" className="waves-effect waves-light btn col">
                <i class="material-icons right">send</i>send
              </button>
            </form>
        </div>
        <div className="col s12 m7 l7">
          <ul className="collection with-header">
            <li className="collection-header"><h4>Post</h4></li>
          </ul>
        </div>
      </div>
    );
  }
};

ReactDOM.render(
  <App />,
  document.getElementById('app')
);

まずはじめにrenderメソッドだけがあるAppクラスを作ります。

各html要素のクラスはMaterializeのためにこのようにしておきます。

すると、こんな感じになります。

4-1

 

4-2.  Firestoreに値を保存する
app.jsx

'use strict';

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  databaseURL: "YOUR_DATABASE_URL",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};

firebase.initializeApp(firebaseConfig);
const dbRef = firebase.firestore().collection('app');

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '',
      list: [],
    }
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  };

  handleChange(event) {
    this.setState({value: event.target.value});
  };

  handleSubmit(event) {
    const text = document.getElementById('text');
    const val = this.state.value;
    if ( val === '') {
      return;
    }
    dbRef.add({
      id: this.state.list.length+1,
      name: "me",
      text: val,
      created: firebase.firestore.FieldValue.serverTimestamp(),
    })
    .then(() => {
      this.setState({ value: ''});
      text.focus();
    });
    this.setState({ list: this.state.list});
    event.preventDefault();
  };

  render() {
    return (
      <div className="row" style={{margin: "1em"}}>
        <div className="col s12 m5 l5">
          <form onSubmit={this.handleSubmit} style={{marginTop: "4em"}}>
              <textarea
                type="text"
                id="text"
                className="materialize-textarea col s9"
                value={this.state.value}
                onChange={this.handleChange}
              />
              <button type="submit" className="waves-effect waves-light btn col">
                <i class="material-icons right">send</i>send
              </button>
            </form>
        </div>
        <div className="col s12 m7 l7">
          <ul className="collection with-header">
            <li className="collection-header"><h4>Post</h4></li>
            <div>
              {this.state.list}
            </div>
          </ul>
        </div>
      </div>
    );
  }
};

ReactDOM.render(
  <App />,
  document.getElementById('app')
);

const firebaseConfigで先ほど設定したFirebaseのkeyなどを定義します。


 

firebase.initializeApp(firebaseConfig);
const dbRef = firebase.firestore().collection(‘app’);

 

↑ここでFirestoreへの参照を定義しておきます。


 

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);

 

handleChangeとhandleSubmitを定義してconstructorでbindしておきます。


 

handleSubmitの中でFirestoreへ保存させます。

 

dbRef.add({
 id: this.state.list.length+1,
 name: "me",
 text: val,
 created: firebase.firestore.FieldValue.serverTimestamp(),
})

Firestoreの参照に対してadd(Object)で保存できます。

id -> 保存したものをリストで表示させる際にkeyを与えるために使います。

 

Key は、どの要素が変更、追加もしくは削除されたのかを React が識別するのに役立ちます。配列内の項目に安定した識別性を与えるため、それぞれの項目に key を与えるべきです。

引用:  リストと key – React

 

name -> 投稿者名

text -> 投稿内容

created -> 投稿日時(あとで表示させる際のソートに使います)

 
ちなみに、collection().add()はpromiseを返しますのでthenでtextareaを空にしておきます。


これでページ内のフォームから入力して保存するとFirestoreのコンソールはこのようになります。

この段階ではまだページに表示されません。

4-2

 

4-3. 保存した投稿をリアルタイムにリストで表示する(完成)
app.jsx

'use strict';

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  databaseURL: "YOUR_DATABASE_URL",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};

firebase.initializeApp(firebaseConfig);
const dbRef = firebase.firestore().collection('app');

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '',
      list: [],
    }
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);

    dbRef.orderBy('created').onSnapshot(snapshot => {
      snapshot.docChanges().forEach(change => {
        if (change.type === 'added') {
          this.state.list.push(
            <li
              className="collection-item avatar"
              key={change.doc.id}
            >
              <span className="circle" style={{borderRadius: 0}}>{change.doc.data().name}</span>
              <span className="title">{change.doc.data().text}</span>
            </li>
          );
          this.setState({list: this.state.list});
        }
      })
    });
  };

  handleChange(event) {
    this.setState({value: event.target.value});
  };

  handleSubmit(event) {
    const text = document.getElementById('text');
    const val = this.state.value;
    if ( val === '') {
      return;
    }
    dbRef.add({
      id: this.state.list.length+1,
      name: "me",
      text: val,
      created: firebase.firestore.FieldValue.serverTimestamp(),
    })
    .then(() => {
      this.setState({ value: ''});
      text.focus();
    });
    this.setState({ list: this.state.list});
    event.preventDefault();
  };

  render() {
    return (
      <div className="row" style={{margin: "1em"}}>
        <div className="col s12 m5 l5">
          <form onSubmit={this.handleSubmit} style={{marginTop: "4em"}}>
              <textarea
                type="text"
                id="text"
                className="materialize-textarea col s9"
                value={this.state.value}
                onChange={this.handleChange}
              />
              <button type="submit" className="waves-effect waves-light btn col">
                <i class="material-icons right">send</i>send
              </button>
            </form>
        </div>
        <div className="col s12 m7 l7">
          <ul className="collection with-header">
            <li className="collection-header">
              <h4>Post</h4>
            </li>
            <div>
              {this.state.list}
            </div>
          </ul>
        </div>
      </div>
    );
  }
};

ReactDOM.render(
  <App />,
  document.getElementById('app')
);

 
constructor内で投稿を取得しリストを作成します。
 
FirestoreのonSnapshotを使うことでFirestore内の変更を検知し、変更内容が追加であれば、stateで定義したlist(配列)の中にli要素として追加していきます。
 
そして、render内ではstate.listを表示する箇所があるので、追加された内容がこちらに反映されます。
 
4-3
 

目次へ戻る

 

セキュリティについて

Firestoreのセットアップ時にも表示されますが、今回の内容の通りに作成するとkeyなどが分かれば誰でも読み書きできるようになっています。

 

無料プランはその名の通り課金されることはありませんが、読み書きや保存の量に対して制限があります。個人が一人で使っている分にはなかなか制限に達することはないかと思いますが、keyなどが悪用された場合は制限に達して動かなくなる、ということもなくはありません。

 

では一般的に考えるとkeyを隠せばいいのでは?と思いますが、Firebaseのセキュリティに関してはkeyを隠すのではなく、Databaseのルール設定によって安全にしていくことが望まれるようです。

 

本記事の通りにいくと作成から30日以内ならだれでも読み書きができるルール設定になっていますが、これはコンソールから変更ができます。

シミュレータもありますのでそこでいろいろ試してみましょう。

 

security

 

今回実装しておりませんが、このようなアプリで好ましいのは認証ユーザのみ読み書きができる等でよろしいかと思います。

 

ルール設定については公式ページ等を参考になさってください。

参考: Firebase Database Security Rules API

 

目次へ戻る

 

最後に

これで完成となりましたが、少し変えるだけで複数ユーザによるチャットアプリにもなったりしますので、いろいろアレンジしてみてください。

目次へ戻る

 

 

これまでに書いた記事(主にスプレッドシートやGASに関する内容)はこちらからどうぞ。

Google Apps Script はじめました

GASを使ってGmailから本文の一部を抜き出す

スプレッドシート独自の便利な関数

QUERY関数の便利な使い方

スプレッドシートを使って簡単なスクレイピングをしてみよう

【GAS】正規表現を使ってGmailの本文から文章を抜き出す

【簡単】GASでスプレッドシートの送信リストからメールを送る

【GAS】正規表現を使って複数行の文章をGmailから抜き出す

【GAS】Gmailの受信トレイにあるメールの添付ファイルを自動でGoogleドライブへ保存する

【難しくない】GASでwebスクレイピングして正規表現でデータを集める

【GAS】OCRを使った画像の文字取得を自動化する

【GAS】お手軽なOCRの自動化をスプレッドシートで扱いやすくする

ReactとFirebase(Cloud Firestore)を使って独り言WEBアプリをつくる
 

それでは!


この記事の作者

アバター
ヨシモト


総記事本数:17

コメントをどうぞ

知識の記事

  1. gahag-0075639565
    サブスクリプションの整理をしよう
  2. E5DxwzVgVHYHr411581668296_1581668370
    わたしは公家ではありませんでした(多分)
  3. PAK86_smonitatocode20140517_TP_V
    All in One SEO Packの必要性
  4. Young woman using a laptop computer. Graphic designer. UX design.
    仕事で大活躍のGoogle Chrome拡張機能を紹介

おすすめ記事

  1. ウィルス画像
    横浜のブラック企業 Initial Site(イニシャルサイト)から『コロナハラスメント』の内部告…
  2. E5DxwzVgVHYHr411581668296_1581668370
    わたしは公家ではありませんでした(多分)
  3. 1b8493ba94ff68762824c3c7274b3128-e1499313795652
    イオンの保存容器はジップロック超えた!?※主観的視点