ECMAScript
vue.js
Firebase
Vuex
VuexFire

Vue.js+Vuex+VuexFireでTodoList

最近、Firebaseを勉強中です。Vuexも勉強中です。
その2つをつなぐためのVuexFireというライブラリを組み込んで、TodoListを作ってみました。

とりあえず現状だと接続先のFirebaseデータベース残してるから動くけど、その内消すかも。

コード比較

VuexFireの有無でのStoreのコードを比較してみます。

VuexFireなし
import Vuex from 'vuex';
import Vue from 'vue';
import { ADD_TODO, REMOVE_TODO } from './action-types';

Vue.use(Vuex);

export default new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production',
  state: {
    todos: [],
  },
  mutations: {
    [ADD_TODO](state, text) {
      const todo = {
        id: 0,
        text,
      };
      if (this.state.todos.length !== 0) {
        todo.id = this.state.todos[this.state.todos.length - 1].id + 1;
      }
      this.state.todos.push(todo);
    },
    [REMOVE_TODO](state, id) {
      this.state.todos = this.state.todos.filter(todo => id !== todo.id);
    },
  },
  actions: {
    [ADD_TODO](context, text) {
      context.commit(ADD_TODO, text);
    },
    [REMOVE_TODO](context, id) {
      context.commit(REMOVE_TODO, id);
    },
  },
  getters: {
    getTodos: state => state.todos,
  },
});
VuexFireあり
import Vuex from 'vuex';
import Vue from 'vue';
import * as firebase from 'firebase';
import { firebaseMutations, firebaseAction } from 'vuexfire';
import { ADD_TODO, REMOVE_TODO } from './action-types';

const config = {
  databaseURL: 'https://vue-fire-test-3c227.firebaseio.com/',
};
const firebaseApp = firebase.initializeApp(config);
const db = firebaseApp.database();
const todosRef = db.ref('todo');

Vue.use(Vuex);

const INIT_TODO = 'INIT_TODO';
const myPlugin = store => store.dispatch(INIT_TODO, 'test');

export default new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production',
  state: {
    todos: [],
  },
  mutations: {
    ...firebaseMutations,
  },
  actions: {
    [INIT_TODO]: firebaseAction(({ bindFirebaseRef }) => {
      bindFirebaseRef('todos', todosRef, { wait: true });
    }),
    [ADD_TODO]: firebaseAction((context, text) => {
      todosRef.push(text);
    }),
    [REMOVE_TODO]: firebaseAction((context, key) => {
      todosRef.child(key).remove();
    }),
  },
  getters: {
    getTodos: state => state.todos,
  },
  plugins: [myPlugin],
});

Mutation

一番大きな違いはmutationsの処理がほぼ消えたことです。
そもそもTODOのデータ構造でFirebase側でつけられたキーを使うことができるため、idを管理する必要がなくなったというのもあります。
しかし、本当の理由はstateへの変更はActionで呼び出すbindFirebaseRefによるFirebaseとの連携処理に限定されるからです。
その処理は非同期になるため、すべてActionのほうに書くことになります。

Action

ADD_TODOの処理を見てみます。

todosRef.push(text);

todosRefはFirebaseのdb参照でそこに対してpushを行っています。
todosRefが変更されると、(INIT_TODOでbindingを行っているため)VuexFireが自動的に変更の内容がmutations.todosに連携されます。
つまり、Firebaseにデータを連携させるのは普通にfirebaseの機能を使って行い、Firebase側から現在の状態をstateにbindさせるときにVuexFireの機能を使っています。

VuexFireを入れたときVuexはどうなるか?

VuexFireを使ったときのデータの流れの図です。

まず、Vuexだけの時はこんな感じ。
image.png

Getter経由でComponentにアクセスすることやAPIとの接続が省略されているところを公式サイトとちょっと変えています。

VuexFireが入るとこんな感じ。
image.png

いままで、Actionから呼び出してたMutationがVuexFireのほうから呼び出されて、stateの状態が変更される感じです。
あくまでFirebaseと接続されるデータに着目した図なので、Firebaseと接続されないstateがあれば今まで通りのデータフローになります。

VuexFireの役割

上でも少し述べましたが、VuexFireの役割はFirebase側からstateへのバインディングなので、双方向のやり取りをやってくれるわけではないようです。
あくまでFirebaseへの変更はfirebaseの機能を使うということですね。
最初、VuexFireというものがあると聞いて想像していたのは、設定を書けばstateの状態を監視して自動でFirebaseと連携してくれるみたいなものを想像していたのですが、そういう感じではないようです。
ふつうのVueFireだとどうなんだろう。
なんかVuexとの連携の保証がされないみたいな話がどこかにあって、そっちは使ってないけど。

参考

Vue.js(v2.x.)を使ってメモアプリを作ってみた
vuexfire で Firebase と Vuex をサクッと試してみる
Vue.jsとVuexでTodoListを作ってみた