Vue.jsでFirebaseに画像・コメント投稿機能を実装

# 作っているアプリ
自分の精神が不安定なので、思考マップを見せびらかすアプリを作りました。
自分の思考マップを公開したいという同期で作成しました。
本音は某就活サイトの話を聞いて成果物無いのやべえと実感したからです。
必要な機能はユーザー機能設定とタイムライン機能、思考マップは画像とし、画像とコメントを投稿する機能が必要になります。

最初はAxiosでREST APIで実現しようとしましたが、Firebase CloudStoreにREST APIでアクセスしようとしている方があまりいなかったので、渋々Firebase tool Kitで実装することに。

# Docker
Dockerで環境構築を実現しましたが、詳細はまた別の回に投稿します。

# firebase-toolsをインストール
```
# npm install --save firebase-tools
```
# main.js
main.jsはindex.htmlを読み込んで、ここでApp.vueをコンパイルしてHTMLに描画されます。
vueや作成したstoreディレクトリ内のcomment.jsを活用するには、
importしておく必要があります。
ここでfirebaseの認証設定も記述しておきます。

```javascript:main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import firebase from 'firebase'

Vue.use(Vuetify,{
  iconfont: 'md',
})
Vue.config.productionTip = false

// Firebase設定
let firebaseConfig = {
//設定をここでメインに書きます
}

firebase.initializeApp(firebaseConfig);
export const db = firebase.firestore()

```

# Store
画像アップロード機能はSession内に書くようにして、他の処理と差別化を施しました。

```javascript:store/modules/comments.js

import firebase from 'firebase'

const state =  {
    imageURL: null,
    uploadFile: null,
    infoMsg: null,
}

const getters = {
    imageURL: state => state.imageURL,
    uploadFile: state => state.uploadFile,
}

const mutations = {
    //アップロードする画像を選択
    uploadFile (state, uploadFile){
        state.uploadFile = uploadFile
    },

    //画像アップロード機能
    upload (state){
        if (!state.uploadFile) {
            state.infoMsg = '選択してください'
            return;
        }
        //Firebase Store上での画像URL
        state.imageURL = 'tmp/' + state.uploadFile.name
        //アップロード
        var storageRef = firebase.storage().ref().child(state.imageURL);
        storageRef.put(state.uploadFile).then(function (snapshot) {
            console.log('Uploaded a blob or file!');
            console.log(snapshot)
        });
    },

    //画像アップロードファイルとStorage先保存URLを初期化
    initialize (state) {
        state.imageURL = null;
        state.uploadFile = null;
    },
}

const actions = {
    //mutation : uploadへ
    selectFile: function ({ commit }, e) {
        e.preventDefault();
        let files = e.target.files;
        commit('uploadFile', files[0])
    },
    upload: function ({ commit },) {
        commit('upload');
    },
    initialize: function({ commit }){
        commit('initialize')
    },
}

export default {
    state,
    getters,
    mutations,
    actions
}
```

大事なのはこの箇所！！
今回の場合、Firebase CloudStore内の"/tmp/〇〇"の箇所に画像が保存されるため、
DataBaseにCloudStore内の保存先"/tmp/〇〇"を置きました。
CloudStoreには保存先"/tmp/〇〇"を指定して、アップロードするわけです。

ログインをしないと、思考マップを投稿できない仕様となっています。
これはログアウトしている状態だと、IdTokenを有していないためです。
ログイン機能に関しては別の回にてまとめる予定です。


```javascript
//Firebase Store上での画像URL
state.imageURL = 'tmp/' + state.uploadFile.name

//アップロード
var storageRef = firebase.storage().ref().child(state.imageURL);
storageRef.put(state.uploadFile).then(function (snapshot) {
            console.log('Uploaded a blob or file!');
            console.log(snapshot)
}
```

# 投稿画面
今回はvuetifyを使ってデザインを構築していますが、関連がないので割愛。

二つの関数を軸に動いています。
特にアップロード関連は後者。

- @change = "selectFile"　→ 画像を選択した瞬間に発動
- @change = "createMap"　→ 画像＋コメントを送信+画像のアップロード



```vue:Posts.vue
<template>
    <v-app>
         <v-card width='90%' class="mx-auto mt-5">
            <v-card-title>
                <h2 class='display-1 item--center'>Post</h2>
            </v-card-title>
            <v-card-text>
            <v-form>
                <v-text-field id='title'
                              label='マインドマップのテーマ'
                              v-model='title'
                              type='text'/>
                <v-textarea id='comment'
                              label='マインドマップの詳細'
                              v-model='comment'/>
            <input type="file" name="image"  @change="selectFile">
            </v-form>
            </v-card-text>
            
            <v-card-actions>
                <v-btn class='info item--center' @click='createMap'>投稿</v-btn>
            </v-card-actions>
        </v-card>
    </v-app>
</template>

<script>
import { db } from '../main'
import moment from  'moment'


export default {
    data(){
        return{
            //ユーザー名とユーザー固有のID
            displayName: null,
            userUID: null,
            //メール・Mapのタイトル・コメント・日時・画像URL
            email: null,
            title: null,
            comment: null,
            moment: null,
            imageURL: null,
            maps: {},
        }
    },
    computed: {
        //iDTokenはログインしていないと投稿できないシステムに
        idToken: function() {
            return this.$store.getters.idToken;
            },
　　　　　//現在のユーザーを取得
        getUser: function() {
            return this.$store.getters.displayName;
            }
        },
    methods: {
        //選択したファイルを保存
        selectFile(e){
            this.$store.dispatch('selectFile',e)     
        },
        //思考マップのタイトル・コメント・日時・画像ファイルをアップロード
        createMap () {
　　　　　　　//stores/modules/comments.jsのupload actionを動作
            this.$store.dispatch('upload')
            this.moment = moment().format('YYYY-MM-DD')
            console.log(this);
            db.collection('comments').add({
                name: this.title,
                comment: this.comment,
                user: this.email,
                uid: this.userUID,
                moment: this.moment,
                //stores/modules/comments.jsのimageURLを取得
                imageURl: this.$store.getters.imageURL
            })
            .then(function (docRef){
                console.log(this);
                console.log("Document written with ID: ", docRef.id);
                this.$store.dispatch('initialize');
                
            })
            .catch(function (error) {
                console.log("error", error);
            })
        }
    } 
}
</script>
```

便利だと感じたのは、this.$storeでstore内に作成したgettersやactionをあらゆるファイルから呼び出せる大変汎用性の高い点です。

以下のサイトに詳しく書かれているので、良かったら参考に。
[【Vuex】ストアの4つの概念まとめ【唯一の情報源】](https://qiita.com/kouki-iwahara/items/1a75daaa93657b0b56d7)

stateで保持する値を直接更新・取得せず、gettersを介して取得、mutationを介して更新しているわけですね。

# 投稿画面の様子
<img width="1428" alt="スクリーンショット 2020-06-09 19.33.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253129/fa116ef3-c473-5593-2a44-28b62e47b9c5.png">

# 参考
[【Vuex】ストアの4つの概念まとめ【唯一の情報源】]
(https://qiita.com/kouki-iwahara/items/1a75daaa93657b0b56d7)

[Vue.jsを100時間勉強して分かったこと](https://qiita.com/kskinaba/items/3e8887d45b11f9132012)