Vuex

Firestore

+

2018-08-03 pixiv社内勉強会

@hakatashi

Vuex

Vuex

  • Vue.jsでglobalなステートを管理するための
    デザインパターン+ライブラリ
  • 要するにflux

Firestore

  • Firebaseの一機能として提供される
    データベース
  • Firebase Realtime Database の後継
  • 2017年10月に発表されて以来
    未だにベータ版

Firestore

  • クライアント-サーバー間で
    リアルタイムに同期可能なNoSQL
  • Realtime Database と比較して
    データ構造がだいぶまともに
  • リアルタイム性とスケーラビリティのために
    いろんなものが犠牲になっている

Vue+Firestoreで
できること

Vue+Firestoreで
できること

  • Firestoreでデータベース上のデータが
    クライアントに同期される
  • Vue.jsでクライアントのステートが
    DOMにバインディングされ同期される
  • →リモートのデータベースの内容が
    何もしなくてもクライアントのViewに
    同期される!!!!!

デモ

Firestoreの考え方

  • クライアントが直接DBを読み書きする
  • レコード・テーブルに対するアクセス権限が
    細かく設定できるので、基本はこれで対処可能
  • より複雑なこと (複雑なトランザクション・外部リソースに依存するデータ検証・etc) をやりたい場合は
    Cloud Function にやらせる

書き込み

同期

Firestore

Function

複雑なこと

HTTPリクエスト

Firestoreの利点👍

  • リアルタイム同期
  • オフラインキャッシュ
  • 公式のSDKが優秀
  • レコードに対するアクセスコントロール
  • 無限にスケールする!
    • レコード数が1000万件とかになった時に
      破綻するようなクエリはそもそも
      最初から書けないようになっている (後述)
  • 重い
    • cold start 問題 (最初のデータ取得に数秒かかる)
  • クエリが貧弱
    • SQLやNoSQLで当然のようにできていたことは
      何一つとしてできない
    • Firestoreでできないこと: NOT, !=, OR句, UNIQUE制約, IN, LIKE, RANDOM, EXISTS, MIN(), MAX(), COUNT(), SUM(), JOIN全般 (inner join, outer join), UNION, GROUP BY, $all, $elemMatch, $size, $type

逆に何ができるの

  • 実行可能なクエリ =
    • 複数のフィールドに対する == 演算
    • + 単一のフィールドに対する >=, >, <, <= 演算

Firestore的考え方をする

VuexとFirestoreを繋ぐ

“VuexFireが便利

DBの/counter/counterをVuexの$store.state.counterにbindする

import {firebaseAction, firebaseMutations} from 'vuexfire';

const counterRef = db.collection('counter').doc('counter');

export default new Vuex.Store({
  state: {
    counter: null,
  },

  mutations: {
    ...firebaseMutations,
  },

  actions: {
    bindCounter: firebaseAction(async ({bindFirebaseRef}) => {
      await bindFirebaseRef('counter', counterRef);
    }),
  },
});

bind

使い方

<template>
  <p>Count: {{counter}}</p>
</template>

<script>
export default {
  data() {
    return { initialized: false };
  },
  computed: {
    ...mapGetters(['counter']),
  },
  mounted() {
    this.$store.dispatch('bindCounter').then(() => {
      this.initialized = true;
    });
  },
  destroyed() {
    this.$store.dispatch('unbindCounter');
  },
};
</script>

例えば、Vue Component のmount時にbindして、destroy時にunbindする

コレクション内の
必要なデータのみbindする

export default new Vuex.Store({
  state: {
    data: {},
  },
  actions: {
    bindById: firebaseAction(async ({bindFirebaseRef, state}, id) => {
      // 二重bindを防ぐ
      if (state.data[id]) {
        return;
      }
      const couplingRef = couplingsRef.doc(id);
      await bindFirebaseRef(`data.${id}`, couplingRef);
    }),
  },
});

オブジェクトの形でデータを持つ (賛否両論)
Vuexfireには二重bindを防ぐ機構がないので注意が必要

関連データを同時にbindする

actions: {
  bindCharacter: firebaseAction(async ({bindFirebaseRef, dispatch}, {name}) => {
    // 二重bindを防ぐ (省略)

    const characters = await charactersRef.where('name', '==', name).get();
    const character = characters.docs[0];
    const characterBindPromise = bindFirebaseRef(`data.${character.id}`, character.ref);

    const couplings = await couplingsRef.where(`members.${character.id}`, '==', true).get();
    await Promise.all([
      characterBindPromise,
      ...couplings.docs.map((coupling) => (
        dispatch('couplings/bind', coupling.id, {root: true})
      )),
    ]);
  }),
}

bindと同時にクエリを実行し、全部bindしたらresolveする
UI上でどんどんデータが降ってくる様子が見えるのでエモい (?)

SSR

export default {
  async fetch({store, params}) {
    if (!process.browser) {
      await store.dispatch('characters/bindCharacter', {
        name: params.name,
      });
    }
  },
  ...
};

Nuxt.jsならfetchメソッドに突っ込めば一発
ただgenerateのたびにDBにアクセスして重いのでなんとかしたい

Vuex+Firestoreに対する所感

  • Vuexfireはまだ未熟な部分が多い
  • nuxt generate などと組み合わせれば、
    サーバーレスでDBを持ってSSRまでできる
  • しかもリアルタイムで同期できるのはエモい
  • ただ、すべてをFirestoreに乗せるのは
    データ構造上しんどい
    • 本格的にやりたいなら
      リアルタイムに同期したいデータのみ
      Firestoreに乗せるのでもよいのではないか
  • Firestoreは早く正式版をリリースしろ

おしまい

Vuex+Firestore

By Koki Takahashi

Vuex+Firestore

2018-08-03 pixiv社内勉強会

  • 3,872