【Nuxt.js】firebase基礎編：Cloud Storageで画像アップロード＆取得

🎈 この記事はWP専用です
https://wp.me/pc9NHC-Xy

#前置き
⬇️プロジェクトページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469755/f63f5b14-4bff-29df-a9ef-70f3d28c6fb7.png)
⬇️Firebase Storage画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469755/0181d456-c24a-5ec2-f144-6be61c534370.png)

input type="file" で
選択した画像をFirebase Cloud Storageに
送信＆取得をしていきます🌸

⬇️firebaseのプロジェクト作成などは
　こちらをご覧ください👀
　[【Nuxt.js】firebase導入編(RDB版)：データの追加 取得をしよう](https://qiita.com/aLiz/items/c2b205f34131a852bbda)

公式: [Cloud Storage](https://firebase.google.com/docs/storage/web/start?hl=ja%3Atitle)

参考:
[FirebaseStorageの使い方・Singleを使って実装する。](https://qiita.com/unochanel/items/9246584b8a1153aaa902)
[Vue/Vuex/Nuxtのformでinput type='file'を指定するとv-modelが使えない](https://qiita.com/after666/items/0b9503e8f3026cb1d686)
[firebase Storageへのファイル保存](https://qiita.com/nakada-web/items/01eeeb0e22d0855312e5)
[File APIとFileReader APIの利用](https://www.codegrid.net/articles/fileapi-1)

#firebaseを使う前に
##input type="file"でv-modelは使えない
Nuxtのテンプレートでは
inputにv-modelを使う、
とイメージする方が多いと思います💡

```index.vue
<template>
  <div class="page">
    <form
      class="form"
      @submit.prevent="submitImg"
    >
      <input
        // ここがエラーになります
        v-model="image"
        type="file"
      >
      <button
        type="submit"
        class="button"
      >
        click
      </button>
    </form>
  </div>
</template>
```

しかしv-modelは
type="file"のサポートをしておりません🌀

##@changeを使う
v-modelの代わりに
@changeを使用します。

submitの前に、
@changeで
選択した画像の
Fileオブジェクトを
確認しています👀

**e.target.files[0]**
選択ファイルを配列形式で取得
まぁ画像のタイトルとかの情報が
入ってるものです💫

> eventのtargetには「files」というプロパティが用意されています。これは「FileList」という複数ファイルを管理するオブジェクトが設定されています。このFileListでは、配列のようにファイルが管理されているのです。
http://libro.tuyano.com/index3?id=384001&page=2

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469755/13d8cb86-537f-43cb-38bb-cf1356386c3c.png)

```index.vue
<template>
  <div class="page">
    <form
      class="form"
      @submit.prevent="submitImg"
    >

      <input
        type="file"
        accept="img/*"
        @change="changeImg"
      >
      <button
        type="submit"
        class="button"
      >
        click
      </button>
    </form>
  </div>
</template>


<script>
export default {
  data () {
    return {
      thumbnail: '',
    }
  },
  methods: {
    changeImg (e) {
      this.thumbnail = e.target.files[0]
      console.log(this.thumbnail)
    },
    submitImg () {
      // ここでsubmitする
    },
  },
}
</script>

<style lang="scss" scoped>
</style>
```

#firebaseの確認
##put()メソッドで送信
送信にはput()メソッドを使います。
引数にはFileまたはBlobインスタンスを
指定する必要があるようです。
[Upload from a Blob or File](https://firebase.google.com/docs/storage/web/upload-files?hl=ja%3Atitle#upload_from_a_blob_or_file)

今回はFileオブジェクトの読み込みをする
**FileReader API**を使用します。
APIといっても
元から使用できるので
axiosを使う必要はありません💫☝️

submitのコードイメージは
こんな感じですね。
ということで
次のFileReaderを
詳しく見ていきましょう💡

```index.vue
submitImg () {
      let storage = firebase.storage()
      let storageRef = storage.ref().child('file.png')
      storageRef.put([FileReaderAPIを使用したファイル])
        .then(res => console.log(res))
        .catch(error => console.log(error))
},
```

#FileReaderの確認
**new FileReader()**
fileを実際に読み込みます

**readAsDataURL**
fileをURLデータに変換する

**onload**
読み込みが成功、完了

**result**
読み込み結果

##inputに入れた画像を表示させる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469755/91c33105-99fa-0d78-7070-63220af7991a.png)

取得と送信用の画像データを
分かりやすくするために
新しくdata postData thumbnailを作成。
FileReaderを使って
画像情報を表示させています。
readAsDataURLで
送信用のURLデータに変換しています。

```index.vue
取得と送信用の画像データを
分かりやすくするために
新しくdata postData thumbnailを作成。
FileReaderを使って
画像情報を表示させています。
readAsDataURLで
送信用のURLデータに変換しています。
```

#FileReader + firebase
##送信

```index.vue
<template>
  <div class="page">
    <form
      class="form"
      @submit.prevent="submitImg()"
    >
      <input
        type="file"
        accept="img/*"
        @change="changeImg"
      >
      <button
        type="submit"
        class="button"
      >
        click
      </button>
   </form>
    <div>
      <p>{{ thumbnail }}</p>
      <p>{{ postData.thumbnail }}</p>
      <img :src="postData.thumbnail" alt="">
    </div>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  data () {
    return {
      thumbnail: '',
      postData: {
        thumbnail: '',
      },
    }
  },
  methods: {
    changeImg (e) {
      this.thumbnail = e.target.files[0]

      if (this.thumbnail) {
        const reader = new FileReader()
        reader.onload = () => {
          this.postData.thumbnail = reader.result + ''
        }
        reader.readAsDataURL(this.thumbnail)
        console.log('選択完了')
        this.submitImg(this.thumbnail)
      }
    },
    submitImg (thumbnail) {
      let storage = firebase.storage()
      let storageRef = storage.ref().child('file.png')
      storageRef.put(thumbnail)
        .then(res => console.log(res))
        .catch(error => console.log(error))
    },
  },
}
</script>

<style lang="scss" scoped>
</style>
```

###送信時に起きたエラー
とりあえずchildとか使わずに
firebase.storage().ref()に
putした時のエラー。
⬇️
FirebaseError: Firebase Storage: The operation 'put' cannot be performed on a root reference, create a non-root reference using child, such as .child('file.png'). (storage/invalid-root-operation)

翻訳

FirebaseError：Firebase Storage：操作 'put'はルート参照に対して実行できません。.child（ 'file.png'）などの子を使用して非ルート参照を作成してください。 （ストレージ/無効なルート操作）

…
ということで
非ルート参照にする必要があるようです。

##取得
[Download Data via URL](https://firebase.google.com/docs/storage/web/download-files?hl=ja%3Atitle#download_data_via_url)

getDownloadURL()を使用します。

```index.vue
<template>
  <div class="page">
    <form
      class="form"
      @submit.prevent="submitImg()"
    >
      <input
        type="file"
        accept="img/*"
        @change="changeImg"
      >
      <button
        type="submit"
        class="button"
      >
        click
      </button>
      <div>
        <p>{{ thumbnail }}</p>
        <p>{{ postData.thumbnail }}</p>
        <img :src="postData.thumbnail" alt="">
      </div>
    </form>
    <div>
      <button
        class="button"
        @click="getImg()"
      >
        取得
      </button>
      <img :src="getData.thumbnail" alt="">
    </div>
  </div>
</template>

<script>
import firebase from '@/plugins/firebase'

export default {
  data () {
    return {
      thumbnail: '',
      postData: {
        thumbnail: '',
      },
      getData: {
        thumbnail: '',
      },
    }
  },
  methods: {
    changeImg (e) {
      this.thumbnail = e.target.files[0]

      if (this.thumbnail) {
        const reader = new FileReader()
        reader.onload = () => {
          this.postData.thumbnail = reader.result + ''
        }
        reader.readAsDataURL(this.thumbnail)
        console.log('選択完了')
        this.submitImg(this.thumbnail)
      }
    },
    submitImg (thumbnail) {
      let storage = firebase.storage()
      let storageRef = storage.ref().child('file.png')
      storageRef.put(thumbnail)
        .then(res => console.log(res))
        .catch(error => console.log(error))
    },
    getImg () {
      let storage = firebase.storage()
      let storageRef = storage.ref().child('file.png')
      storageRef.getDownloadURL()
        .then(res => {
          console.log(res)
          this.getData.thumbnail = res
        })
    },
  },
}
</script>

<style lang="scss" scoped>
</style>
```

#まとめ
firebaseは公式ガイドをしっかり見れば
基礎的なことはできてきます🎈🧸
まだよく分からないという方は
こちらの記事で慣れましょう🌟
[Firebase Realtime Database](https://nuxt.alizlab.com/%e3%80%90nuxt-js%e3%80%91firebase%e5%b0%8e%e5%85%a5%e7%b7%a8rdb%e7%89%88%ef%bc%9a%e3%83%87%e3%83%bc%e3%82%bf%e3%81%ae%e8%bf%bd%e5%8a%a0-%e5%8f%96%e5%be%97%e3%82%92%e3%81%97%e3%82%88%e3%81%86/)
[Cloud Firestore](https://nuxt.alizlab.com/%e3%80%90nuxt-js%e3%80%91firebase%e5%b0%8e%e5%85%a5%e7%b7%a8firestore%e7%89%88%ef%bc%9a%e3%83%87%e3%83%bc%e3%82%bf%e3%81%ae%e8%bf%bd%e5%8a%a0-%e5%8f%96%e5%be%97%e3%82%92%e3%81%97%e3%82%88%e3%81%86/)

余談ですがQiitaの他に
GitHubでTSのコードを
参考にしました。
TS独自の書き方に慣れたいです💭
TSできれば無駄なチェックが不要になるし…

変数の後ろのびっくりマーク❗️の意味を知りました💡
[TypeScriptの変数の末尾の"!"(エクスクラメーション/感嘆符)の意味](https://qiita.com/zigenin/items/364264a6cf635b962542)