gotoshin

主に学んだ事の自分メモ用です。記事に書くまでも無いような事はhttps://scrapbox.io/study-diary/に書いてます。

Vue(2系)×TypeScript×Firebaseでチャットアプリ

まずは基本的な記述方法を理解する

以前少しだけTSにチャレンジした事がありましたが、ほとんど忘れていたので再学習。

とりあえず型システムについての箇所をさらっと読んで後は手を動かしてみる。

アプリ作成にチャレンジ

どんなアプリ?

以下の記事で紹介されているVue.js×Firebaseで作られたチャットアプリをTSで作ろうと思います。

Vue.js+Firebaseで認証付きチャット | 基礎から学ぶ Vue.js

TSの書き方について

色んな記事がありましたが、今回はTSに触れる事が目的のため、もっともVueらしく書けるこちらの記事を参考に作成。

[Vue+TypeScript] Vue.extend で Vue らしさを保ちつつ TypeScript で書くときの型宣言についてまとめた - Qiita

環境構築

  • Frirebase
  "dependencies": {
    "firebase": "^7.21.0",
    "save": "^2.4.0",
    "vue": "^2.6.11"
  },

実装

<template>
  <div id="app">
    <header class="header">
      <h1>Chat</h1>
      <div v-if="user.uid" key="logout">
        {{ user.displayName }}
        <button type="button" @click="doLogout">ログアウト</button>
      </div>
      <div v-else key="login">
        <button type="button" @click="doLogin">ログイン</button>
      </div>
    </header>

    <transition-group name="chat" tag="div" class="list content">
      <section v-for="{ key, name, image, message } in chat" :key="key" class="item">
        <div class="item-image"><img :src="image" width="40" height="40"></div>
        <div class="item-detail">
          <div class="item-name">{{ name }}</div>
          <div class="item-message">
            <p tag="div">{{ message }}</p>
          </div>
        </div>
      </section>
    </transition-group>

    <form action="" @submit.prevent="doSend" class="form">
      <textarea
        v-model="input"
        :disabled="!user.uid"
        @keydown.enter.exact.prevent="doSend"></textarea>
      <button type="submit" :disabled="!user.uid" class="send-button">Send</button>
    </form>
  </div>
</template>
<script lang="ts">
  import Vue from "vue"
  import firebase, { analytics } from "firebase"

  export type UserType = {
    displayName: string;
    uid: string;
    photoURL: string;
  }
  export type ChatType = {
    key: string;
    name: string;
    image: string;
    message: string;
  }
  export type SnapType = {
    val(): Function & MessageType;
    key: string;
    message: object;
  }
  export type MessageType = {
    message: string;
    name: string;
    image: string;
  }
  export default Vue.extend({
    name: 'App',
    data() {
      return {
        user: {} as UserType,
        input: "" as string,
        chat: [] as ChatType[]
      }
    },
    created() {
      firebase.auth().onAuthStateChanged((user: any): void => {
        this.user = user ? user : {};
        const refMessage = firebase.database().ref('message')
        if (user) {
          this.chat = []
          // message に変更があったときのハンドラを登録
          refMessage.limitToLast(10).on('child_added', this.childAdded)
        } else {
          // message に変更があったときのハンドラを解除
          refMessage.limitToLast(10).off('child_added', this.childAdded)
        }
      })
    },
    methods: {
      doLogin(): void {
        const provider = new firebase.auth.TwitterAuthProvider()
        firebase.auth().signInWithPopup(provider)
      },
      doLogout(): void {
        firebase.auth().signOut()
      },
      scrollBottom(): void  {
        this.$nextTick(() => {
          window.scrollTo(0, document.body.clientHeight)
        })
      },
      childAdded(snap: SnapType): void {
        const message = snap.val()
        this.chat.push({
          key: snap.key,
          name: message.name,
          image: message.image,
          message: message.message
        })
        this.scrollBottom()
      },
      doSend(): void  {
        if (this.user.uid && this.input.length) {
          firebase.database().ref('message').push({
            message: this.input,
            name: this.user.displayName,
            image: this.user.photoURL
          }, () => {
            this.input = ''
          })
        }
      }
    }
  })
</script>
  • methodは戻値が無いためデータ型はvoid
  • onAuthStateChangedの引数は色々試したがany以外通せず。。。 笑

良いと思った点

  • TSに関してはこれを作っただけだと知識不足もあって正直良さが分からなかった。ただ、一旦TS触ってみれたので、今後通常のJSを書きながらここTSだったら防げたな〜という気付きが得られるかもしれない
  • 今回の目的とは違うがfirebaseに関してこれまで認証とホスティングでしか使った事無かったけど、リアルタイム通信が簡単に出来てすごいと思った

イマイチと思った点

  • 型を通すってテスト通すくらい負担に感じた。実際今回は一部型を通すの妥協したw。
  • 外部APIとの連携が特に難しいと感じた。今回だとfirebaseの関数の型をどう通すかggってもよく分からないものもいくつかあった

今後

  • 今後もちょいちょい触りながら既存プロジェクトへどう部分的に導入していくかを考えられるくらいには知識を得たい