🦊

Firebase × Vue.js でリアルタイムに反映される掲示板を作る

2022/05/29に公開

はじめに

https://tanaken.me/web/791/
を参考にリアルタイムに反映される掲示板を作りました。
firebaseのバージョンや、ファイルの参照が違う、etc・・で詰まった点が多いので、自分なりにまとめ直してみたので、やってみてください。

vue.jsのプロジェクト作成

ローカルのvue.jsのプロジェクトを作成します。

構成

board-app
  |- index.html
  |- index.css

雛形となるhtmlを作成。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>board_app</title>
  <link href="index.css" rel="stylesheet">
</head>
<body>
  <div id="board">
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  <script src="index.js"></script>
</body>
</html>
CDNを使ってVue.jsを読み込んでいます。

投稿機能の実装

vue.jsを用いて投稿機能を実装していきます。 勉強がてら、コンポーネントに分割した構成にします。 firebaseは後ほど実装するので、この段階ではデータの永続化がなされていません。

index.htmlの<div id="board"></div>を以下のように編集しましょう。

index.html
  <div id="board">
        <h2>掲示板</h2>
        <ul class="lists" style="list-style-type: none">
            <board-list v-for="list in lists" v-bind:name="list.name" v-bind:message="list.message"
                v-bind:date="list.date">
            </board-list>
        </ul>
        <board-form v-on:input="doAdd"></board-form>
    </div>

index.jsを作成して、次のように編集します。

index.js
Vue.component('board-list', {
  template: '<li>{{name}} {{date}}</br>{{message}}</li>',
  props: ['name', 'message', 'date']
})

Vue.component('board-form', {
  template: '<div class="form-area">名前 : <input v-model="name"> </br>コメント: \
  <textarea v-model="message"></textarea> </br><button v-on:click="doAdd">書き込む</button></div>',
  data: function(){
    return{
      message: '',
      name: ''
    }
  },
  methods: {
    doAdd: function(){
      this.$emit('input', this.name, this.message)
    }
  }
})

var board = new Vue({
  el: '#board',
  data: {
    name:'',
    message: '',
    date: '',
    lists: [
    ]
  },
  methods: {
    doAdd: function(name, message){
      var now = new Date();
      this.lists.push({
        name: name,
        message: message,
        date: now.getMonth()+1 + '月' + now.getDate() + '日' + now.getHours() + '時' + now.getMinutes() + '分'
      })
    }
  }
})

上から順に、board-listコンポーネント、board-formコンポーネントを定義し、new VueでルートになるVueインスタンスを作成し、#boardと紐づけています。

formから入力された値は、ルートインスタンスに渡され、つぎの形で、dataオプションに登録されているlists配列に追加されます。

name: 入力した名前
message: 入力したコメント
date: 時間

時間は date: now.getMonth()+1 + ‘月’ + now.getDate() + ‘日’ + now.getHours() + ‘時’ + now.getMinutes() + ‘分 で取得します。

これを、v-forを用いて一つずつ要素を取り出しpropsを通して子コンポーネントに受け渡して繰り返し表示させています。しかし、この状態だとブラウザをリロードした段階でデータがリセットされてしまいます。そこで、firebaseを導入してデータの永続化を行なっていきましょう。

Firebaseでデータを永続化する

1. Firebaseにアクセスして、プロジェクトを作成する

2. スニペットの取得


webをクリックします

firebaseConfit={}の部分をコピーして保存しておいてください。

4. リアルタイムデータベースの作成



作成します


データベースを作成後、読み込み書き込みを許可するためにルールで

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

{
  "rules": {
    ".read": false,
    ".write": false
  }
}

に変更し、公開ボタンを押します。

実装

index.htmlを次のように編集しましょう。(index.jsは削除してください)
(index.jsに切り分けたかったのですが、値の渡し方がわからずまとめることにしました)
firebaseConfig={}の部分は、先程コピーして保存したものに書き換えてください。

index.html
<html lang="ja">

<head>
    <meta charset="utf-8">
    <title>board_app</title>
    <link href="index.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>

    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.2/firebase-app.js";
        import { getAnalytics } from "https://www.gstatic.com/firebasejs/9.8.2/firebase-analytics.js";
        import { getDatabase, ref, set, onValue, push } from "https://www.gstatic.com/firebasejs/9.8.2/firebase-database.js";

        const firebaseConfig = {
            apiKey: "自分の値",
            authDomain: "自分の値",
            projectId: "自分の値",
            storageBucket: "自分の値",
            messagingSenderId: "自分の値",
            appId: "自分の値",
            measurementId: "自分の値"
        };

        const app = initializeApp(firebaseConfig);
        const database = getDatabase(app);
        const analytics = getAnalytics(app);

        Vue.component('board-list', {
            template: '<li class="board-list"><div class="board-list__upper">名前:{{name}}{{date}}</div>{{message}}</li>',
            props: ['name', 'message', 'date', 'id'],
        })

        Vue.component('board-form', {
            template: '<div class="form-area">名前 : <input v-model="name"> </br>コメント: \
    <textarea v-model="message"></textarea> </br><button v-on:click="doAdd">書き込む</button></div>',
            data: function () {
                return {
                    message: '',
                    name: ''
                }
            },
            methods: {
                doAdd: function () {
                    this.$emit('input', this.name, this.message)
                    this.message = ''
                    this.name = ''
                }
            }
        })

        var board = new Vue({
            el: '#board',
            data: {
                lists: []
            },
            created: function () {
                var vue = this;
                const starCountRef = ref(database, 'board');
                onValue(starCountRef, (snapshot) => {
                    vue.lists = snapshot.val();
                    console.log(snapshot.val())
                });
            },
            methods: {
                doAdd: function (name, message) {
                    var now = new Date();
                    push(ref(database, 'board'), {
                        name: name,
                        message: message,
                        date: now.getMonth() + 1 + '月' + now.getDate() + '日' + now.getHours() + '時' + now.getMinutes() + '分'
                    });
                }
            }
        })
    </script>
</head>
<body>
    <div id="board">
        <h2 class="board-title">掲示板</h2>
        <ul class="lists" style="list-style-type: none">
            <board-list v-for="(list, key) in lists" :key="key" :name="list.name" :message="list.message"
                :date="list.date">
            </board-list>
        </ul>
        <board-form v-on:input="doAdd"></board-form>
    </div>
</body>
</html>

注目するべき箇所は3つほどです。

1.firebaseへの書き込み

push(ref(database, 'board'), 渡したい値);でpushの引数に渡されたデータをfirebaseへ書き込むことができます。

2.firebaseから読み込み

const starCountRef = ref(database, 'board');
onValue(starCountRef, (snapshot) => {});とすることで、イベントの発生時(データベースの変更時)に、リッスンすることができます。つまり、データベースの変更を自動で検知して、onに渡した関数を実行させることができます。これまでは、フォームに入力された値は直接ルートインスタンスの持つlists配列に収められていましたが、firebaseを経由してlists配列に収めバインドされるように変更しました。こうすることで、クライアント側に依存せずに、複数のクライアントでで同期した掲示板を運営することができるようになりました!!

3.keyの設定

v-forでコンポーネントを繰り返し表示させているので、一意なkeyを与えなくてはなりません。先ほどfirbaseから取得した値は、listsに次のような形で収められています。

firebaseにデータを保存した際に一意となる値が割り振られているのこの値をkeyとして用いています。

これで、リアルタイムに更新される掲示板を作成することができたはずです。 登録されたデータは、firebaseのワークスペース上から確認することが可能です。

見た目

最後に、簡単なcssを追加して見た目を少しだけよくしておきます。

index.css
body{
  background: #FFC766;
}

#board{
  width: 70%;
  padding: 10px 0;
  border: 1px solid #333;
  border-radius: 0.75em / 0.75em;
  margin: 0 auto;
  background: #EFEFEF;
}

.board-list{
  margin: 15px 0;
}

.board-list__upper{
  font-weight: 600;
}

.form-area, .board-title{
  width: 90%;
  margin: 0 auto;
}

参考

https://firebase.google.com/docs/database/web/read-and-write#web-version-8_2

https://zenn.dev/hiro__dev/articles/605161cd5a7875

https://tanaken.me/web/791/

補足

参考にした記事は2018年でFirebaseのversionが異なっていて、全然うまく行きませんでした。(v8だった模様、2022現在はv9)
v8系のコードとv9系のコードは互換性の問題があるようだったので、firebase公式のページを見ながらv8系のコードをv9系のコードにコツコツ変換しました。

Discussion