Help us understand the problem. What is going on with this article?

Vue と Nuxt と Firebase でサクッとアプリを作る

More than 3 years have passed since last update.

Vue.js での開発はいくつか経験したことがあるが、Nuxt.js は触ったことがなかったので入門してみた。
SSR やユニバーサルといったところは置いておいて、ひとまず簡単な SPA を作ってみてメモや雑感をまとめました。

以下は nuxt@v1.0.0-rc11 での内容になります。

作ったもの

Civirization ボードゲームの技術ツリーを管理・閲覧するためのツール。
もともと2年ほど前に Angular + Firebase で作っていたものをベースに、Vue.js + Nuxt.js + VuexFire で書き直したものです。

ゲームの内容はさておき、各自が手元のスマホで操作するとリアルタイムに技術ツリーが同期・共有される。
認証やバリデーションなど細かいところは詰めてないですが、身内で使うには十分かなと。

ソースコードはこちら

Nuxt.js とは

詳しくは公式サイトに任せるとして、ざっくり説明すると

  • Vue.js 製のフロントエンドフレームワーク
  • SSR対応
  • ルーティングやストアの自動生成などを行う
  • ES6/ES7 のトランスパイル
  • SASS, LESS, Stylus などの CSS プリプロセッサのサポート

などがあります。

Nuxt.js はソースコードのバンドルに Webpack を利用しており、CSS プリプロセッサや babel-loader を内包しています。
これらの設定は Nuxt.js が隠蔽しており簡単に使い始めることができます。
(適宜設定を追加することも可能です。)

Firebase と VuexFire

Firebase は BaaS / mBaaS と呼ばれるアレ。
なにやらたくさん機能があるけど、今回使ったのはリアルタイムデータベース (RTDB) のみ。
誰かがデータを更新すると自動で他のユーザにも更新が反映される。

VuexFire は Firebase の Vuex バインディング。
データベースが更新されると、その内容を自動で Vuex のストアに反映してくれる。
※双方向バインディングではない点に注意。詳細は後述。

画面遷移

今回は以下の2ページ構成にしています。

  • Index (トップページ: 開始ボタンがあるだけ)
  • Game (ゲーム画面: 技術ツリーを表示)

Game 画面はクエリパラメータから gameKey を受け取ります。
pages ディレクトリは以下のとおり。

/ROOT/
  └ /pages/
      ├ index.vue
      └ game.vue

Nuxt.js では、ファイルの配置によって自動的にルーティングが生成されます。

今回は Github Pages 上で公開する予定だったので、静的なルーティングのみ利用しています。

Vuex と VuexFire の設定

ドキュメントに従い、Vuex ストアをモジュールモードで設定します。
store/index.js を作成し、store, mutations, actions を export するだけで Vuex ストアが自動生成されます。

あわせて VuexFire の設定も行います。
mutations に firebaseMutations を含め、firebaseActionbindFirebaseRef でバインドする state のキーと Reference
を設定します。

ソースコードの抜粋でわかりにくいですが、LOAD_GAME アクションを実行すると RTDB の games/<gameKey>/playersstate.players にバインディングされるようになっています。

store/index.js
import { firebaseMutations, firebaseAction } from 'vuexfire'
import db from '~/plugins/firebase'
import { SET_GAME_ID } from './mutation-types'
import { LOAD_GAME, SET_PLAYERS_REF } from './action-types'

const gamesRef = db.ref('games')

export const state = () => ({
  gameId: null,
  players: [],
})

export const mutations = {
  ...firebaseMutations
}

export const actions = {
  async [LOAD_GAME] ({commit, dispatch}, gameKey) {
    const gameRef = gamesRef.child(gameKey)
    const gameSnapshot = await gameRef.once('value')
    const game = gameSnapshot.val()
    if (game && ('players' in game)) {
      commit(SET_GAME_ID, gameRef.key)
      dispatch(SET_PLAYERS_REF, gameRef.child('players'))
      return true
    }
    return false
  },

  [SET_PLAYERS_REF]: firebaseAction(({bindFirebaseRef, commit}, playersRef) => {
    bindFirebaseRef('players', playersRef)
  }),
}
plugins/firebase.js
import firebase from 'firebase'

const config = {
  databaseURL: 'https://civbg-support.firebaseio.com/'
}

if (firebase.apps.length === 0) {
  firebase.initializeApp(config)
}

export default firebase.database()

データの更新

VuexFire は Firebase → Vuex への片方向バインディングのみを行います。
そのためデータの更新は state の更新ではなく、Firebase API を直接叩いて行う必要があります。

おそらくトランザクション制御や一括書き込みなどを Vuex で表現できないので、あえて片方向にしているんだと思います。
#最初、双方向バインディングだと思い込んでいてハマりました…。

store/index.js
export const actions = {
  ...

  async [ADD_TECH] ({state, commit}, {player, level, techId}) {
    const playersRef = gamesRef.child(state.gameId).child('players')
    await playersRef.child(player['.key']).transaction((p) => {
      if (p) {
        p.tree = {first: [], second: [], third: [], fourth: [], ...p.tree}
        p.tree[level].push(techId)
      }
      return p
    })
  },

  ...
}

Firebase は要素が null や [] の場合キーごと消えてしまうので都度初期化しています。

VuexFire を利用する場合のデータフローは以下の記事が図解されておりわかりやすかったです。

雑感

ざっと触った感じでは、開発の立ち上がりは非常に楽でした。
Webpack のビルド設定まわりはいつも面倒に感じていたので、省略できるのはありがたいです。

ただ、まだ発展途上を思わせるところもあった。

デバッグのために一時的に Minify (Uglify) をオフにしたかったのだが、client.config.js を見てみると、そもそもオフにする設定が v1.0.0-rc11 にはなかった。
dev ブランチの client.config.js には設定が追加されていたので、近いうち利用できるようになるかも。

追記:下書きしている途中に Nuxt.js v1.0.0 が正式リリースされたようです。上記の内容も取り込まれていました!

正式リリースされたようなので、次は業務で使ってみようかな。

wiro34
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away