20200529のvue.jsに関する記事は13件です。

BootstrapVueのダサいイメージコンポーネントをクールなデザインにカスタマイズしてみた

スクリーンショット 2020-05-29 16.25.36.png

Vueバージョン確認

npm list vue

まずは上記コマンドでバージョンの確認

twinzlabo@0.1.0 /Users/twinzlabo

── vue@2.6.11

BootstrapVueの導入

BootstrapVueの導入がまだの方のために念のため導入方法書いときますね

とりあえずコピペして環境を整えてください

main.js
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue)
npm install vue bootstrap-vue bootstrap

以上でBootstrapVueの導入は完了です

BootstrapVueのダサいイメージをクールなデザインにカスタマイズ

すでに上の方で確認してもらったかと思いますが、

BootstrapVueの非常にダサいImageコンポーネントをスタイル修正を行うことでクールなデザインに編集していきましょう
スクリーンショット 2020-05-29 16.13.19.png

デフォルトの上の画像をhoverしたら下の画像のように色がつくようにカスタマイズしていきます
スクリーンショット 2020-05-29 16.13.11.png

この感じなかなかクールですよね

では早速コードをコピペしていきましょう

<template>
  <div>
    <b-img src="https://picsum.photos/300/150/?image=41" fluid alt="Fluid image"></b-img>
  </div>
</template>
<style>
img {
  margin-right: 10px;
  margin-bottom: 30px;
  display: inline-block;
  width: 500px;
  height: 400px;
  background-size: contain;
  background-repeat: no-repeat;
  cursor: pointer;
  transition: all 200ms ease-in;
  filter: grayscale(1) opacity(.8);
}

img:hover {
  filter: grayscale(0) opacity(1);
}
</style>

これだけです

いかがでしたでしょうか?ちゃんと同じようなデザインになりましたか?

今回も超簡単にカスタマイズできましたね

以上です

参考記事(より応用的な実装が希望の方)

【Vueデザイン/コピペだけ】白黒画像一覧でhoverすると色がつくCSSアニメーション実装を徹底解説
←トップ画像のような実装ができます
【Vue/BootstrapVueコピペのみ】Bootstarap導入からシンプルな画像一覧画面の実装方法までを徹底解説

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js 2 + Vuex State Management Tutorial by Example

Vue is an excellent open-source front-end library to build user-friendly web/mobile applications. It follows the same component-based architecture as other popular frameworks do.

click here to read more:
https://www.positronx.io/vue-js-vuex-state-management-tutorial-by-example/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue i18n Tutorial – How to Build Multi-Lingual Vue.js App

Learn how to translate the Vue.js app in multi-language using the vue-i18n plugin.

click here to read more:
https://www.positronx.io/vue-i18n-tutorial-how-to-build-multi-lingual-vue-js-app/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js 2 + Firebase Authentication Tutorial Example

Learn how to integrate Firebase Authentication in Vue.js 2+ application. We will also learn to register a user, login a user and send password reset mail and logout from firebase app using Vue.js application.

click here to read more:
https://www.positronx.io/vue-js-firebase-authentication-tutorial-example/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deploy Vue.js 2+ App to Firebase Hosting in Less Than 5 Minutes

In this tutorial, we are going to explain step by step how to deploy Vue.js application on Free Firebase Hosting using Firebase console.

click here to read more:
https://www.positronx.io/deploy-vue-js-app-to-firebase-hosting/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】 DOMを直接操作 $el $ref

はじめに

データバインディングを利用することでDOMの更新は効率化されるが、直接DOMを参照したい場合もある。例えば、画面上の要素の位置や高さはDOMを直接参照しなければ分からない。
DOMに直接アクセスするにはインスタンスプロパティの$el$refsを使用する。
この2つはDOMを参照するため、ライフサイクルのmounted以降でなければ使用できない

ライフサイクルについてはこちら

$el(ルートを参照)

$elを使用することでインスタンスやコンポーネントのルートを参照することができる。

sample.html
 <div id="app">
   <p>Hello</p>
 </div>
sample.js
new Vew({
  el: 'app',
  mounted: function() {
  // <div id="app"></div>が出力される
  console.log(this.$el)
  }
})
ルートとは

HTMLはツリー構造になっているが、その最上位に位置するのがルートである。HTMLドキュメントではhtml要素がルートにあたる。

$refs(DOMの要素を参照)

ルート以外のDOMの要素を参照する場合は、ref属性と$refsを使用する。
refはDOM要素を参照するための特別な属性で$refsのキーとなる。

sample.html
 <div id="app">
   <!-- 対象となる要素にref属性で名前をつける -->
   <p ref="hello">Hello</p>
 </div>
sample.js
new Vue({
  el: 'app',
  mounted: function() {
    // <p>Hello</p>が出力される
    console.log(this.$refs.hello);
  }
})

動的にDOMを参照

動的にする場合、refはv-bindディレクティブを使って:refと記述する。
v-forで要素を繰り返し表示している場合はv-for部分は配列構造になっているのでscript内でアクセスするにはthis.$refs[ref属性でつけた名前]と記述する。

sample.html
<div v-for="item in items" :key="item.id">
  <!-- 動的にするためrefにv-bindをつける -->
  <p :ref="name">{{ item.name }}</p>
</div>
sample.js
new Vue ({
  el: 'app',
  data: {
    items: [
      {
        id: '001',
        name: 'apple'
      },
      {
        id: '002',
        name: 'orange'
      }
    ]
  },
  mounted: function() {
    // <p>apple</p>が出力される
    console.log(this.$refs[name][0]);
  }
})

$elと$refsは一時的な変更

$elと$refsは一時的な変更なので、データが更新されたり、アプリケーションが操作されて再描画されると上書きされる(変更がリセットされる)。仮想DOMではなくリアルDOMを参照しているからである。

リアルDOMと仮想DOM

VueにはリアルDOM仮想DOMという2つのDOMが存在する。

リアルDOM

リアルDOMは実際にブラウザを描画しているものである。
Vueを使用しないでリアルDOMのみで描画している場合、変更を加えた際はHTML内の全てを読み込むので描画に時間がかかる。

<リアルDOMのみの描画の流れ>
1. HTML
2. リアルDOM
3. WEBページ

仮想DOM

仮想DOMは擬似的に作られたDOM。仮想DOMの中には変更前と変更後の2つのDOMが用意されていて、この2つのDOMの差分のみがリアルDOMに反映されるようになっている。
差分のみを読み込み反映するので、描画のスピードが速い。

<仮想DOMを用いた描画の流れ>
1. HTML
2. 仮想DOM   →変更前のDOM & 変更後のDOM(この2つの差分のみリアルDOMへ反映)
3. リアルDOM
4. WEBページ

こちらでわかりやすく説明されている

上記の説明を踏まえて動作を確認してみる

sample.html
<div id="app">
  <!-- ボタンをクリックするとhandleClickメソッドが実行される -->
  <button @click="handleClick">カウントアップ</button>
  <!-- ボタンをクリックするとshowの値が反転する -->
  <button @click="show = !show">v-show 表示/非表示</button>
  <button @click="show = !show">v-if 表示/非表示</button>
  <!-- 値の真偽値によって要素を表示/非表示(非表示の場合display:none;の状態) -->
  <p ref="count" v-show="show">0<p>
  <!-- 値の真偽値によって要素を表示/非表示(非表示の場合DOMから要素が削除されている状態) -->
  <p ref="count" v-if="show">0<p>
</div>
sample.js
new Vue({
  el: 'app',
  data: {
    show: true
  },
  methods: {
    handleClick: function() {
      // ref属性で紐づけたcountを変数countに代入
      var count = this.$refs.count;
      // count(pタグ)の中のテキストを整数(10進数)にして1プラスする
      if(count) {
        count.innerText = parseInt(count.innerText, 10) + 1
      }
    }
  }
})

上記のコードではカウントアップボタンをクリックするとpタグの中の数値が1ずつアップしていく仕組みになっている。
v-ifで表示を切り替えている部分はDOMから要素自体が削除されている(仮想DOMを切り替えている)ので、非表示にして表示し直すとpタグの数値は0にリセットされる。
それに対しv-showで切り替えている部分はdisplay:none;で見えなくなっているだけで要素自体は存在している(リアルDOMには何の影響も与えていない)ので、非表示にして表示し直すとカウントアップした数値はそのまま維持される。
これらのことから$refs$elの変更は一時的だということがわかる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js】firebase基礎編(Auth版):Googleログインをできるようにしよう

前置き

Frame 6.png
メールアドレスログインと
要領は同じです!
コードもこちらに付け足します✍️
https://note.com/aliz/n/n7f4ae08ba828

ということで
firebaseAuthを使います?

使わなくてもできますが
その場合はやることが
3倍には増えます…笑
Google Cloud Platformから
OAuthクラウドIDを発行したり
tokenの照会をするコードを書いたり??

firebaseAuthを使うと
すっっごく!!!
簡単に!!!
実装できます?

Step1

Googleサインインについてはこちら
firebase Google Sign-In

まずはAuthentication > Sign-in method
Googleを選択

スクリーンショット 2020-05-18 18.34.31.png

メールアドレスを選択し保存するだけ!

スクリーンショット 2020-05-18 18.36.46.png

【解説/store/index.js】
準備は整ったので
公式Contens2つめに参りましょう♪
Handle the sign-in flow with the Firebase SDK

必須項目:1, 5
Optional:2, 3, 4

ということで必須項目だけ
Vuexにコピペしていきます。

・actions loginGoogleを作成
 ログインできたら
 ログイン状態をtrueにしたいので
 それを行うcheckLoginをdispatchで呼ぶ
・不要な物を削除
 コメント
 セミコロン(;)
 var token
 var user

store/index.js
import firebase from '~/plugins/firebase'

export const state = () => ({
 user: {
   uid: '',
   email: '',
   login: false,
 },
})

export const getters = {
 user: state => {
   return state.user
 }
}

export const actions = {
 login({ dispatch }, payload) {
   firebase.auth().signInWithEmailAndPassword(payload.email, payload.password)
     .then(user => {
         console.log('成功!')
         dispatch('checkLogin')
       }).catch((error) => {
         alert(error)
       })
 },
 loginGoogle ({ dispatch }) {
   var provider = new firebase.auth.GoogleAuthProvider()
   firebase.auth().signInWithPopup(provider).then(function (result) {
     dispatch('checkLogin')
   }).catch(function (error) {
     console.log(error)
   })
 },
 checkLogin ({ commit }) {
   firebase.auth().onAuthStateChanged(function (user) {
     if (user) {
       commit('getData', { uid: user.uid, email: user.email })
       commit('switchLogin')
     }
   })
 },
}

export const mutations = {
 getData (state, payload) {
   state.user.uid = payload.uid
   state.user.email = payload.email
 },
 switchLogin (state) {
   state.user.login = true
 },
}
Login.vue
<template>
<div class="login">
  <p
    v-if="user.login"
    class="text"
  >
    {{ user }}
  </p>
  <form
    v-else
    class="form"
    @submit.prevent
  >
    <label class="label">
      <span class="label">
        email
      </span>
      <input
        class="input"
        type="text"
        v-model="email"
      >
    </label>
    <label class="label">
      <span class="label">
        password
      </span>
      <input
        class="input"
        type="password"
        v-model="password"
      >
    </label>
    <button
      class="button"
      type="submit"
      @click="login"
    >
      Login
    </button>
    <button
       class="button"
       type="submit"
       @click="loginGoogle"
    >
       googleでログイン
    </button>
  </form>
</div>
</template>

<script>
export default {
 computed: {
  user () {
    return this.$store.getters['user']
  },
 },
 data () {
  return {
    email: '',
    password: '',
  }
 },
 methods : {
  login (email, password) {
    this.$store.dispatch('login', {email: this.email, password: this.password})
  },
  loginGoogle () {
    this.$store.dispatch('loginGoogle')
  },
 }
}
</script>
index.vue
<template>
<div class="page">
  <Login />
  <p
    v-if="user.login"
    class="text"
  >
    ログインに成功!
  </p>
</div>
</template>

<script>
import Login from '~/components/Login.vue'

export default {
components: {
  Login: Login,
},
computed: {
  user () {
    return this.$store.getters['user']
  },
},
}
</script>

ログインはこれだけです?
アカウント作成や、
ログアウトの仕方はまた別記事にて♪

次回予告

【Nuxt.js】Nuxt文法編:v-for
6/2(火)公開予定です!
お楽しみに♪

記事が公開したときにわかる様、
フォローをお願いします??

https://twitter.com/aLizlab

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue CLIプロジェクトでコンパイル時にコードフォーマットをかける

前提

@vue/cli 4.4.1
vue create <projectName>

で作成したプロジェクト

  • Manually select features
  • Linter/Formatter にチェック
  • Pick a linter / formatter config > ESLint + Prettier
  • Pick additional lint features > Lint on save

lintエラーがある場合コンソールにメッセージが出るだけで
手動での修正が面倒なので、ファイル保存時に自動修正したいです。

ググればVScodeの設定でやる方法がわんさかあるのですが、
今回はエディターに依存したくなかったのでwebpack経由で自動修正をします。

なので、ファイル保存時ではなくコンパイル時ということになります。
また、どこからもimportされていないファイルや設定系ファイルなど、
webpackを通らないファイルに関しては自動修正されません。

設定方法

vue.config.js

Vue CLIでは vue.config.jsconfigureWebpackでwebpackの設定をマージできます。
Configuration Reference | Vue CLI

ということでプロジェクトルートに vue.config.js ファイルを作成して以下を記述。

vue.config.js
module.exports = {
  configureWebpack: {
    module: {
      rules: [
        {
          enforce: 'pre',
          test: /\.(jsx?|tsx?|vue)$/,
          exclude: /node_modules/,
          loader: 'eslint-loader',
          options: {
            fix: true
          }
        }
      ]
    }
  }
}

.eslintrc.js

デフォルトのママ

eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    'eslint:recommended',
    '@vue/typescript/recommended',
    '@vue/prettier',
    '@vue/prettier/@typescript-eslint'
  ],
  parserOptions: {
    ecmaVersion: 2020
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
}

.prettierrc

プロジェクトルートに .prettierrc ファイルを作成してお好みの設定を記述

.prettierrc
{
  "semi": false,
  "arrowParens": "always",
  "singleQuote": true
}

おしまいける

以上で設定完了です。

保存前.vue
<script lang="ts">
// 略
  private hoge = "hoge";
// 略
</script>
保存後.vue
<script lang="ts">
// 略
  private hoge = 'hoge'
// 略
</script>

これでnpm run serveでサーバを立ち上げてる間、ファイル保存時に自動フォーマットが効いてくれます。
もちろん npm run serve, npm run build 実行時にも効きます。

冒頭にも記載した通りwebpackを経由しないファイルは修正されないので、
lintコマンドを活用しましょう

参考

Configuration Reference | Vue CLI

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Vue.js]Object.assignの配列コピーで地味にハマったメモ

リアクティブに配列をコピーしたくて使用。
「空の配列」ひとつに対し、値を格納した配列を複数用意。
タブ切り替え時、コピーする配列を変更。

test.vue
let a = [1, 2, 3, 4, 5];
let b = [6, 7, 8];
let c = [9, 10];

タブ切り替え時、コピーする配列(target部分)を変える。
Object.assign(this.array, target);

その配列の要素数をviewで表示するというような実装を行ったところ、
正しい要素数が表示されずに困った。

表示結果
<p>{{array.length}}</P>
    ↓ ↓ ↓
一番要素数が多いaの配列を格納すると、
それ以降はどのタブに切り替えても、aの要素数から変更されない

原因

参考資料にもある通り、
Object.assign()メソッドはコピーを行うので、
今ある要素に上書きされる形になる。
そりゃ、要素数に変更がないわけだ・・・

test.vue
let a = [1, 2, 3, 4, 5];
let b = [6, 7, 8];
let c = [9, 10];

①aをarryにコピーする
Object.assign(this.array, a);
  → this.arry[ 1, 2, 3, 4, 5];

②次はbをコピーする
Object.assign(this.array, b);
  → this.arry[ 6, 7, 8, 4, 5]; //上書きされているため、要素数は変わらない

対策

配列に格納する前に、初期化処理を入れるようにした。
これで正しい要素数を表示できるようになった。

test.vue
// 配列初期化
this.array.splice(-this.arry.length);
//コピー
Object.assign(this.array, target);

最後に

実際は格納した配列を子に渡すなど、処理が複雑になっているため、
こんな簡単なことに気付くのが遅れてしまいました・・・
勉強あるのみですね!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Vue.js]Object.assignの配列コピーで地味にハマったメモ[追記]

リアクティブに配列をコピーしたくて使用。
「空の配列」ひとつに対し、値を格納した配列を複数用意。
タブ切り替え時、コピーする配列を変更。

test.vue
let a = [1, 2, 3, 4, 5];
let b = [6, 7, 8];
let c = [9, 10];

タブ切り替え時、コピーする配列(target部分)を変える。
Object.assign(this.array, target);

その配列の要素数をviewで表示するというような実装を行ったところ、
正しい要素数が表示されずに困った。

表示結果
<p>{{array.length}}</P>
    ↓ ↓ ↓
一番要素数が多いaの配列を格納すると、
それ以降はどのタブに切り替えても、aの要素数から変更されない

原因

参考資料にもある通り、
Object.assign()メソッドはコピーを行うので、
今ある要素に上書きされる形になる。
そりゃ、要素数に変更がないわけだ・・・

test.vue
let a = [1, 2, 3, 4, 5];
let b = [6, 7, 8];
let c = [9, 10];

①aをarryにコピーする
Object.assign(this.array, a);
  → this.arry[ 1, 2, 3, 4, 5];

②次はbをコピーする
Object.assign(this.array, b);
  → this.arry[ 6, 7, 8, 4, 5]; //上書きされているため、要素数は変わらない

対策

配列に格納する前に、初期化処理を入れるようにした。
これで正しい要素数を表示できるようになった。

test.vue
// 配列初期化
this.array.splice(-this.arry.length);
//コピー
Object.assign(this.array, target);

最後に

実際は格納した配列を子に渡すなど、処理が複雑になっているため、
こんな簡単なことに気付くのが遅れてしまいました・・・
勉強あるのみですね!!

追記

コメントよりご指摘いただきました下記の方法でなら、
初期化処理不要で、配列のコピーができました!!
こちらの方がコードがスッキリしていいですね:relaxed:
slice()の参考資料

追記
let a = [1, 2, 3, 4, 5];
let b = [6, 7, 8];
let c = [9, 10];

①配列aのコピーをする
this.array = a.slice();
→this.array[1, 2, 3, 4, 5]

②①の後、this.arrayに配列bを再度格納する
this.array = b.slice();
→this.array[6, 7, 8]  
//上書きではなく、コピー元の配列全体を切り出して新しく配列を生成してくれた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】ナビゲーションガード(beforeRouteEnter)を使って前のページのURLを取得する

やりたいこと

VueRouterで遷移するページで、一つ前のページのURLなどを取得して、
それに応じてページ内のパーツの表示非表示を切り替えたい。

beforeRouteEnterで取得しようとしたが・・・

今回はあるComponentのみで、前のURLが必要だったので、VueRouterのナビゲーションガードのbeforeRouteEnterを使用してみました。
公式のドキュメント

hogeComponent.vue
<template>
  <div>
    // 前のURLに応じてここの表示非表示を切り替えたい
    <div v-if="prevRoute.name =='hoge'"></div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      prevRoute: null,
    };
  },
  created() {
  },
  beforeRouteEnter(to, from, next) {
    next(vm => {
      vm.prevRoute = from;
      console.log(vm.prevRoute);
    });
  },
  methods: {
  }
};
</script>

これでthis.prevRoute.name前のページで表示していたComponentの名前
this.prevRoute.path前のURLが取得できます。

しかし、ここで一つ問題が・・・!
nextに渡したコールバック関数はVueライフサイクルフックのmountedよりも後に実行されてしまうので、
ページを描画するタイミングではprevRoute.nameprevRoute.pathが存在しませんよ〜
というエラーが出てしまう。。。。
beforeRouteEnterのnextコールバックの実行タイミングはこちらの記事を参考にさえていただきました。
ちなみに、コンポーネント描画後であれば、this.prevRoute.nameなどで取得できます!

Vuexを使ってみる

そこで、beforeRouteEnterで取得した前のページのデータをVuexのStateで管理し、
computedでVuexのStateからデータを取得するという方法をとる事にしました。
もっと簡単な方法があればぜひ教えてください・・・!

hogeComponent.vue
<script>
//省略
  beforeRouteEnter(to, from, next) {
    // ここでVuexに前のページの情報をStateに渡す!!
    next(vm => {
      vm.prevRoute = from;
    });
  }
</script>

実装方法

hogeComponent.vue
<script>
import store from "../../../store/index";
import { mapMutations, mapGetters } from "vuex";

export default {
  data() {
    return {
      prevRoute: null
    };
  },
  beforeRouteEnter(to, from, next) {
    store.commit("route/setPrevRoute", from);
    next(vm => {
      vm.prevRoute = from;
    });
  },
  computed: {
    prevRouteChild() {
      return this.$store.getters["route/getPrevRoute"];
    }
  }
};
</script>
store/modules/route.js
const state = {
    prevRoute: []
};

const mutations = {
    setPrevRoute(state, from) {
        state.prevRoute = from;
    }
};

const actions = {};

const getters = {
    getPrevRoute: state => state.prevRoute
};

export default {
    namespaced: true,
    state,
    mutations,
    getters,
};
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import route from "./modules/route";

Vue.use(Vuex);

export default new Vuex.Store({
    modules: {
        route
    }
});

これで初期描画のタイミングで表示非表示を切り替えることができました!
何か間違ってたら、是非教えてください^^

参考ページ

beforeRouteEnterのnextコールバックの実行タイミング

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

天界の様子が見れる秘密のサイトつくった。

FireShot Capture 001 - Email_Password Authentication Example - stupefied-almeida-771bd9.netlify.app.png

秘密だから特定の人しか見れないようにしてます

サイト:www.tenkai.ml
[Emall:demo@demo.jp Pass:demo00 でログインできます。(怖くないよ)]

ログインすると...

FireShot Capture 003 - Email_Password Authentication Example - stupefied-almeida-771bd9.netlify.app.png

天界の様子(大阪エリア)が確認できます。
えぇ...。天界とは、天気です。

天界(大阪エリア)の窓をスクロールすると...

FireShot Capture 004 - Email_Password Authentication Example - stupefied-almeida-771bd9.netlify.app.png
猫。
(天気によって、出てくる猫が変わります。ぜひ、晴れの日にサイトを覗きにきてください。)

サイトの仕組み

1.最初のログイン画面は Firebase Authentication 使ってます。
参考にさせていただいた記事->https://qiita.com/shima-07/items/2c344b0ad306b201a065

2.天界(大阪エリア)の情報は OpenWeatherMap の API を取得しています。
参考にさせていただいた記事->https://www.tam-tam.co.jp/tipsnote/html_css/post16405.html

3.天界(大阪エリア)の小窓は、html を、もう一つデプロイして、それを埋め込んでいます。
参考にさせていただいた記事->https://gray-code.com/html_css/view-another-html-on-current-html/

4.サイトのデプロイは Netlify というサービスを使いました。
参考にさせていただいた記事->https://qiita.com/oganyanATF/items/7fb681e863d8681c9039
(Netlify で html をデプロイする時は、index.html の名称でないと、うまくデプロイできないので気をつけてください。)

おまけ

今回のサイトの GitHub を公開します。
・メインサイト(ログイン機能含む)のリポジトリ->https://github.com/cazmura/ten-kai-login
・埋め込みサイトのリポジトリ->https://github.com/cazmura/ten-kai

最後まで読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsとFirebaseを併用したらDataの取り扱いに困った話

表題の通りです。
Vue.jsとGoogleのFirebaseを利用して画像を保存するモノを作ってる時に、全然うまくいかなかったので、
その備忘録として残しておきます。
あとで思い返したら単純に書き方が間違ってただけなんですが。。
初心者なので暖かめの目で多めにみて頂けたらと思います。
おかしいところとかあればぜひご指摘ください。

環境

Mac OS X Catalina 10.15.4
Vue.js 2.6.11
Vue CLI 4.3.1
AWS Cloud9 Firebase

やりたい事

買ったコーヒーをメモるためのアプリを作ろうとしてました。
Firebaseに画像を保存し、画面読み込みor更新した際に画面に表示させたい。
その最低限の準備です。

保存の実装

事前にVue CLIにFirebaseを読み込ませます。

yarn add firebase --save

もしくはこちらで
npm install firebase --save

Firebaseの設定はいろいろありますが、こちらの記事を参考に爆速で構築させて頂きました。
Vue.js + Firebase を使って爆速でユーザ認証を実装する

トップ画面に、firebaseに保存した画像を表示させるため、まずは保存します。

CoffeeRegister.vue
<template>
  <div class="back">
    <div class="register">
      <h2>Coffee Register</h2>
      <!--名前とか買った店、写真を保存する-->
      <input type="text" placeholder="CoffeeName" v-model="coffeeName">
      <input type="text" placeholder="ShopName" v-model="shopName">
      <input type="file" v-on:change="onFileChange" id="coffee-image" accept="image/jpeg,image/png,image/gif">
      <!--保存ボタン 後述のrecordメソッドで保存-->
      <button v-on:click="record" class="recordbtn">Record</button>
      <!--保存せずに戻るボタン-->
      <button v-on:click="toIndex" class="returnbtn">Return</button>
    </div>
  </div>
</template>

<script>
import firebase from 'firebase' //firebase読み込み

export default {
  name: 'CoffeeRegister',
  data: function(){
    return {
      coffeeName: '',
      shopName: '',
      file: null, // 選択した画像を持っておく変数
      coffeeImageLocation: null, //画像を保存する場所のURLを保存する変数
    };
  },
  methods: {
    toIndex: function() { //トップページに戻るメソッド
      this.$emit('return-click-register', (false));
    },
    // ここから画像ファイルを保存するメソッド
    onFileChange: function(e) {
      const image = e.target.files; //選択された画像ファイルを選択
      this.file = image[0]; //画像ファイルを1つだけ選択

      // Firebase storageに保存するパスを決める
      this.coffeeImageLocation = `coffee-images/${this.coffeeName}`;

    },
    // firebase storageに保存するメソッド
    record: function() {
      //画像をfirebase storageに保存
      firebase
        .storage()
        .ref(this.coffeeImageLocation) //さっき決めたパスを参照して、
        .put(this.file) //保存する
        .then(() => {
          //保存が成功したら、保存した画像ファイルの場所とともにfirebase databaseに保存する準備
          const coffeeData = {
            name: this.coffeeName,
            shop: this.shopName,
            coffeeImageLocation: this.coffeeImageLocation,
            createdAt: firebase.database.ServerValue.TIMESTAMP,
          };
          // ここでfirebase databaseに保存する
          firebase
            .database()
            .ref('beans') //保存する場所を参照して、
            .push(coffeeData) //追加で保存 setメソッドを使うと上書きされる
            .then(() => {
              console.log('saved coffee data.');
              alert('data is registration.');
              this.$emit('success-coffee-registration', false); //親コンポーネントに伝達
            })
            .catch((error) => {
              console.error('cannnot saved.', error);
            });
        })
        .catch((error) => {
          console.error('image uproad error.', error);
        });
    }
  }
}
</script>

firebaseには独特のメソッドがあるし、JSとかと似てるからちょっと困る時がある。

メインページで保存したデータ読み込み

次にトップ画面で読み込みを実装します。

Index.vue
<template>
 ..省略..
<div class="coffee-item">
  <div class="coffee-image-wrapper">
    <img class="coffee-item-image" alt="" v-bind:src="image">
  </div>
  <div class="coffee-detail">
    <div class="coffee-item-name">コーヒー</div>
      <div class="coffee-item-delete-wrapper">
        <button class="btn btn-danger coffee-item-delete">削除</button>
      </div>
  </div>
</div>
..省略..
</template>

<script>
..省略..
import CoffeeRegister from '@/components/CoffeeRegister.vue';

export default {
  ...
  name: 'Index',
  components: {
    ...
    CoffeeRegister,
  },
  data: function () {
    return {
      ...
      lct: '',
      image: '',
    };
  },
  created: function() {
    ...
    this.loadCoffeeView();
    this.downloadCoffeeImages(this.lct);
  },

  methods: {

    // firebase databaseからコーヒーデータをダウンロード
    loadCoffeeView: function() {
      const coffeeRef = firebase
        .database()
        .ref('beans') //firebase database の beans に保存したデータを参照
        .orderByChild('createdAt'); //並び替え
      // 過去に登録したイベントハンドラを削除
      coffeeRef.off('child_added');
      // ここで保存データを抜き取り
      coffeeRef.on('child_added',(coffeeSnapshot) => {
        const coffeeId = coffeeSnapshot.key;
        const coffeeData = coffeeSnapshot.val();

        this.lct = coffeeData.coffeeImageLocation; //ここでURLが抜き取られる
      });
    },
    //firebase storageからコーヒーの画像データをダウンロード
    downloadCoffeeImages: function(coffeeImageLocation) {
      firebase
        .storage()
        .ref(coffeeImageLocation)
        .getDownloadURL()
        .then((data) => {
          console.log(data);
          this.image = data;
        })
        .catch((error) => {
          console.error('cannot download coffee images.', error);
        });
    },

  };
};
</script>

全体像としては上記なのですが、要はライフサイクルフックのcreatedで画像やらデータを読み込んで表示させたかったんです。

Index.vueの抜粋
data: function () {
    return {
      ...
      lct: '',
      image: '',
    };
created: function() {
    ...
    // ここのメソッドで画像URLを[ lct ]に格納して、
    this.loadCoffeeView();
    // ここで画像を抜き取って[ image ]に格納して、v-bindで表示させる予定
    this.downloadCoffeeImages(this.lct);
  },

エラー内容

で、実行したら下記のエラーが。
スクリーンショット 2020-05-28 19.37.13.png

めっちゃ抽象的で困りました。
createdフックでエラーなのはわかります。

試した事

じゃcreatedじゃなかったらいいの?と思ったので、
mountedで記述したんですが、全く同じエラー。
単体で確認しようと思い、v-on:clickディレクティブ使って確認したらちゃんと動いてました。

なんで単体で動くのにライフサイクルフックで動かないの?
と思って、データ抽出メソッドをcreatedにして、URL抽出メソッドをmountedにしてみたら、
mountedで同様のエラーが出ました。
ここのどこかが悪いってわかったので、いろいろ試してみます。

//firebase storageからコーヒーの画像データをダウンロード
    downloadCoffeeImages: function(coffeeImageLocation) {
      firebase
        .storage()
        .ref(coffeeImageLocation)
        .getDownloadURL()
        .then((data) => {
          console.log(data);
          this.image = data;
        })
        .catch((error) => {
          console.error('cannot download coffee images.', error);
        });
    },

いろいろ試したら、.ref(coffeeImageLocation)のところで何かがおかしいみたい。
typeofで、このメソッドに渡される予定の変数がどうなってるか確認したら、undefined

結果

結論からいうと、前述の画像URLを取得して、変数に格納する前に画像参照するメソッドが動いてたから表示されてなかったみたいです。

Index.vue
<script>
...
    // firebase databaseからコーヒーデータをダウンロード
    loadCoffeeView: function() {
      const coffeeRef = firebase
        .database()
        .ref('beans')
        .orderByChild('createdAt');
      // 過去に登録したイベントハンドラを削除
      coffeeRef.off('child_added');

      coffeeRef.on('child_added',(coffeeSnapshot) => {
        const coffeeId = coffeeSnapshot.key;
        const coffeeData = coffeeSnapshot.val();
        console.log(coffeeData);
        const location = coffeeData.coffeeImageLocation;
        console.log('coffee data is:', typeof location);
        console.log(location);

        //firebase storageからコーヒーの画像データをダウンロード
        firebase
          .storage()
          .ref(location)
          .getDownloadURL()
          .then((data) => {
            console.log('data is: ', data);
            this.image = data;
          })
          .catch((error) => {
            console.error('cannot download coffee images.', error);
          });
      });
    },
...
</script>

最終的に全部同じメソッドに記述したらちゃんと表示されました。
変にdataオブジェクトに格納しない方がいいんですね。

このへんの事調べてもよく分からなかったので、ご存知の方がいましたら
ぜひ教えて頂けませんか?

参考

Vue.js + Firebase を使って爆速でユーザ認証を実装する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む