6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Nuxt.jsとFirebaseのFirestoreでリアルタイムダッシュボードを作った

Last updated at Posted at 2020-01-18

やりたいこと

Nuxt.jsとfirestoreを使用して、リアルタイムでダッシュボードを更新するシステムを作る。
リロードとかせずサクサクと!

firestoreの作成

コンソールからプロジェクトを作成して設定

※あとで書く

準備

①firebaseをインストール

yarn add firebase or npm install firebase --save

②plugins/firebase.jsの作成

firebaseの初期設定をします!
firebaseのページの設定画面下部にある情報をそのまま貼り付けてます。
開発環境と本番環境でfirebaseのアカウントを使い分けるためにENVにしています。
.envで書いた環境変数名を書きます

plugins/firebase.js
import firebase from 'firebase/app'
import 'firebase/firestore'

firebase.initializeApp({
  apiKey: process.env.FIRE_BASE_API_KEY,
  authDomain: process.env.FIRE_BASE_AUTH_DOMAIN,
  databaseURL: process.env.FIRE_BASE_DATABASE_URL,
  projectId: process.env.FIRE_BASE_PROJECT_ID,
  storageBucket: process.env.FIRE_BASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIRE_BASE_MESSAGING_SENDER_ID,
  appId: process.env.FIRE_BASE_APP_ID,
  measurementId: process.env.FIRE_BASE_MEASUREMENT_ID
})
const db = firebase.firestore()
export default db

③nuxt.config.jsへ追記

nuxt.config.js
...省略

plugins: [
  ...
  { src: '~/plugins/firebase' }
],

...省略

これでfirestoreを使う準備が整いました!

store/firestoreディレクトリを用意する

私はstore配下をAPI毎にディレクトリを切って、actions.js/getters.js/index.js/mutations.js
と分けて書く方法が好きです。
理由は見やすいし追いやすいからです。

今回はfirestoreの通信についてなので、firestoreとディレクトリを切ります。

まずはactions.js

store/actions.js
import db from '~/plugins/firebase'
// コレクション名をセット
const fireStore = db.collection('dashboard')

export const actions = {
  GET_REALTIME_LIST({ commit }, payload) {
    fireStore
      .where('report_datetime', '>', payload.from)
      .where('report_datetime', '<', payload.to)
      .orderBy('report_datetime', 'asc')
      .orderBy('created_at', 'asc')
      .onSnapshot(
        res => {
          commit('GET_REALTIME_LIST', res)
        },
        error => {
          console.error('GET_REALTIME_LIST', error)
        }
      )
  },
  UPDATE_FIRE_STORE({ commit }, payload) {
    fireStore
      .doc(payload.id)
      .set(payload)
      .then(res => {
        commit('UPDATE_FIRE_STORE', res)
      })
      .catch(e => {
        console.error('UPDATE_FIRE_STORE', e)
      })
  }
}

export default actions

const fireStore = db.collection('dashboard')
ここで接続をするコレクション名をセットします。
今回のコレクション名はdashboardなのでこのように記述。

onSnapshot これがリアルタイム同期をするポイントです。
他にも追加されたものだけ取得、削除されたものだけ取得、とか書き方はありますが
今回のダッシュボードは全体を常に同期していたいのでこう書いてます。

また、firestoreではあまり柔軟にクエリが書けません。
between句もありません><
そこで

.where('report_datetime', '>', payload.from)
.where('report_datetime', '<', payload.to)

このように書くと、between句のように使えます。

次はmutations.js

mutations.js
import {
  GET_REALTIME_INCOMPLETE_LIST_FIRE_STORE,
  UPDATE_FIRE_STORE
} from '~/store/types'

export const mutations = {
  GET_REALTIME_LIST(state, content) {
    state.realtimeFireStore = content.docs.map(res => {
      const fireStore = res.data()
      fireStore.id = res.id
      return fireStore
    })
  },
  UPDATE_FIRE_STORE(state, content) {
    state.updateFireStore = content
  }
}

export default mutations

actionsでmutationsにcommitする際に、res.docsとやってもmutationsでmapで回す前にやっても大丈夫です。

responseではdocsの中に一覧が返ってきます。
そのまま直接参照することは出来ないので、mapで回しながらdata()とやってあげます。

fireStore.id = res.id ここでなぜidを入れているかというと
データを入れる際に、自動生成IDで作成しているので自動生成された一意のIDがデータ内に入っていません。

保持をしておかないとupdateで使用する際に困るので、データ内に突っ込んでます。
自動生成されたIDは、docs内にあります。IDはdata()で参照する中にはなく直接参照できるので、上記のように書きます。

続いてgetters.js

getters.js
export const getters = {
  getRealtimeFireStore: state => state.realtimeFireStore,
  getUpdateFireStore: state => state.updateFireStore
}

export default getters

ここは特にポイントはありません

続いてindex.js

index.js
import Actions from './actions'
import Getters from './getters'
import Mutations from './mutations'

export const state = () => ({
  realtimeFireStore: [],
  updateFireStore: ''
})
export const actions = Actions
export const getters = Getters
export const mutations = Mutations

ここも特にポイントはありません。

これでstoreに準備完了です。

pages/dashboard.vueの作成

さて、やっと画面に取り掛かります。

pages/dashboard.vue
<template>
  <div>
    <div class="contents">
      <h1 class="title">テスト</h1>
    </div>
    <div>
      <table class="dashboard">
        <tr v-for="(dashboardRow, rowIndex) in dashboards" :key="rowIndex">
          <td v-for="(dashboard, dashboardIndex) in dashboardRow" :key="dashboard.id">
            <div class="dashboard-card">
              <input type="text" :value="dashboard.title" @blur="save(rowIndex, dashboardIndex)" />
            </div>
          </td>
        </tr>
      </table>
    </div>
  </div>
</template>

<script>
import moment from 'moment'
import { mapGetters, mapActions } from 'vuex'
import chunk from 'lodash/chunk'
import cloneDeep from 'lodash/cloneDeep'
export default {
  name: "dashboard",
  computed: {
    ...mapGetters('firestore/', [
      'getUpdateFireStore',
      'getRealtimeFireStore'
    ]),
    dashboards: {
      get(){
        const getFireStore = cloneDeep(this.getRealtimeFireStore)
        const dashboards = chunk(filterData, 3).map((dashboardItems, index) => {
          if (dashboardItems.length !== 3) {
            for (let i = 0; i < 4 - dashboardItems.length; i++) {
              dashboardItems.push({})
            }
          }
          return dashboardItems
        })
        return dashboards
      },
      set(dashboards){
        return dashboards
      }
    }
  },
  created() {
    this.getDashboard()
  },
  methods: {
    ...mapActions('firestore/', [
      'UPDATE_FIRE_STORE',
      'GET_REALTIME_LIST'
    ]),
    async getDashboard() {
      this.GET_REALTIME_LIST({
        from: moment().subtract(3, 'days').format('YYYY-MM-DD 00:00:00'),
        to: moment().format('YYYY-MM-DD 23:59:59')
      })
    },
    async save(rowIndex, dashboardIndex) {
      await this.UPDATE_FIRE_STORE(this.dashboards[rowIndex][dashboardIndex])
    },
  }
}
</script>

<style scoped>
table.dashboard {
  width: 100%;
  margin-top: 40px;
}
table.dashboard td {
  width: 30%;
  padding: 20px 14px;
}
.dashboard-card {
  border: 1px solid #cecece;
  border-radius: 4px;
  padding: 10px 6px;
  background-color: #f7f1f1;
  box-shadow: 0px 0px 9px 0px #afafaf;
}
</style>

直近3日のデータを取得しています。

created() {
  this.getDashboard()
},

createdでリアルタイムでgetするactions: GET_REALTIME_LIST しているメソッドを呼びます。
これで常にリアルタイム通信が可能となります。

computed内で、リアルタイムでgetしているデータを(gettersのgetRealtimeFireStore)を加工してあげます。

横3個ずつズラ〜と出したいのでlodashのchunkでわけてあげます。

カード内のテキストボックスでは、文字firestoreに入れてるデータのtitleを表示しています。
この値を変更してフォーカスを外してタイミングで更新メソッド(save)を呼んでいます。

actions:UPDATE_FIRE_STOREを呼んで、更新処理を行います。

この更新がかかると、今actions:GET_REALTIME_LISTがリアルタイムで取得しているので、
getters: getRealtimeFireStoreの値が変更されるため
computedのdashboardsが動き、画面上のカードがリアルタイムで変更されます。

リアルタイムで動いている確認方法

方法①このlocalhostを2つ開き、画面に分けて表示して
片方で更新かけて、もう1つの方でリロードなしに変更がされれば成功です!

方法②firestoreのコンソールで直接データを変更して、リロードなしにダッシュボード内のカードが更新されれば成功です!

以上でリアルタイムダッシュボードの実装は終わりです!

6
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?