20200820のvue.jsに関する記事は12件です。

Vuexってなんだーーー

今回、買い物リスト的なものを作成するにあたり、どうもvuexについて学んだほうがいいとのことで。勉強を・・・始め、ます。
自分の勉強不足、知識不足により、いつも優しく寄り添ってくれたVuejs公式さんでさえ難しくて挫けそう。:persevere:
とりあえずGo:runner_tone3::runner_tone3::walking_tone3:

Vuexってなあに

公式さんによると、

Vuex は Vue.js アプリケーションのための状態管理パターン + ライブラリ
これは予測可能な方法によってのみ状態の変異を行うというルールを保証。

  • state・・・状態、vuejsでいうdata的な。vuexはdataではなくこのstateを管理している。
  • store・・・stateを保管しているところ。
  • mutation(みゅーてーしょん)・・・vuejsでいうメソッド的な。このmutationの処理によって、state(状態)が変化する
  • action・・・mutationにコミットして、実行する。 ペーペケッペッペペ-ペーペペ♪

予測可能な方法

つまり、mutationで予測された範囲(どうやって変化するのか)を定義。
いきなり、関係ない値に変わったりしない。mutationには処理(変更)の内容が定義されている。

stateに直接変化を加えるわけではなく、状態はあくまでも状態。データではない。
commitって英語には引き渡す、委ねる、委託するとかの意味があるようで。
actionによって呼び出され(委ねられた)mutationが状態を変更している。
mutationは状態の変更だけを行う。actionでmutationを呼び出す。

Vuex アプリケーションの中心にあるものはストアです。"ストア" は、基本的にアプリケーションの 状態(state) を保持するコンテナです

stateはいろんなコンポーネントからアクセスできる。propsとかでもらわなくてもいいと。
画期的なのでは・・・?:thinking:

vuexの導入

yarn add vuex

vuejsでvuexを使えるようにしたので、下記をjsに追加。
私は!vuexを使います!使いますからね!?!?!?!?と宣言:triumph::triumph::triumph::triumph:

main.js
import Vuex from 'vuex'

Vue.use(Vuex)

むずかしい・・・
まだVuexさんのことがわからなくて、仲良くなれそうにない

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

【Nuxt.js】データ描画を実装してみた【勉強1】

はじめに

自分が勉強した内容のまとめなので、正確性は保証しません。

こちらのサイトにcodesandboxを埋め込んでいます。

実際の振る舞いを見ながらコードの確認ができるので非常に便利です。

https://moneyhack.tech/Nuxt.js/04-Nuxt/

シンプルにcodesandboxめちゃくちゃおすすめなので、気になる方使ってみてください。

宣言したデータを画面上に描画する

次のコードが最もシンプルなデータ宣言です。

<template>
  <div id="app">{{ message }}</div>
</template>

<script>
export default {
  data: function() {
    return {
      message: "メッセージ"
    };
  }
};
</script>

{{ message }} と記載されている箇所が、宣言したデータを表示しています。

{{ ここはデータ名と一致させる必要があります }}

データを宣言しているのは data: function()の部分です。

message に"メッセージ"を定義しています。

これがデータ描画の基本的な手法です。ここから実際に使われる様々な手法を紹介します。

宣言したデータを属性に反映させる(v-bind)

文字列の展開に加え、要素の属性を束縛(バインディング)することもできます。

class や id の属性を束縛し、動的に色を変更したりすることも可能となります。

<template>
  <div id="app">
    <div v-bind:class="color">文字色が変化するコード</div>
    <button @click="colorChange">変更</button>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      color: "black"
    };
  },
  methods: {
    colorChange: function() {
      this.color = "red";
    }
  }
};
</script>

<style>
.black {
  color: black;
}
.red {
  color: red;
}
</style>

見慣れない記述も増えていますが、今回注目していただきたいのは次のコードです。

<div v-bind:class="color">文字色が変化するコード</div>

v-bind:class によって属性を束縛していることがわかると思います。

今回のコードではボタン操作イベントによって切り替えを行いましたが、

属性の切り替え方法は無限に存在するので、上手に属性の束縛を使っていきましょう。

要素の有無をデータで切り替える(v-if)

要素の条件分岐を使い、要素の有無を切り替えることもできます。

<template>
  <div id="app">
    <span v-if="seen">Now you see me</span>
    <button @click="seenChange">消去</button>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      seen: true
    };
  },
  methods: {
    seenChange: function() {
      this.seen = false;
    }
  }
};
</script>

今回注目していただきたいのは、次のコードです。

<span v-if="seen">Now you see me</span>

v-if によって、要素に条件を追加しています。

seen が true であれば要素を表示し、false であれば非表示としています。

今回はボタン操作により、表示から非表示への切り替えを行っています。

ループ描画をデータで実現する(v-for)

配列データを使い、ループ処理によって描画を行うこともできます。

<template>
  <div id="app">
    <li v-for="todo in todos">{{ todo.text }}</li>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      todos: [
        { text: "Learn JavaScript" },
        { text: "Learn Vue" },
        { text: "Build something awesome" }
      ]
    };
  }
};
</script>

今回注目していただきたいのは次のコードです。

<li v-for="todo in todos">{{ todo.text }}</li>

描画方法は最初に紹介した{{}}で囲う方法ですが、

todos の配列を呼び出し、ループ処理による描画を行っています。

ユーザー入力を反映する(v-model)

ユーザーの入力をリアルタイムに反映させることもできます。

次の sandbox で入力を弄ってみてください。

表示される文字が入力と同時に変化することがわかります。

<template>
  <div id="app">
    <p>{{ message }}</p>
    <input v-model="message">
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      message: "Hello Nuxt.js!!"
    };
  }
};
</script>

今回注目していただきたいのは次のコードです。

<input v-model="message">

通常の input タグに v-model が記載されています。

この v-model がユーザーの入力とアプリケーションの状態の双方向バインディングを提供しています。

ユーザー入力によるメソッドの呼び出し

ユーザー操作によりメソッドを呼び出すこともできます。

v-on:click で呼び出すこともできますし、@click でも呼び出すことができます。

<template>
  <div id="app">
    <p>{{ message }}</p>
    <button v-on:click="reverseMessage">Reverse Message</button>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      message: "Hello Nuxt.js!!"
    };
  },
  methods: {
    reverseMessage: function() {
      this.message = this.message
        .split("")
        .reverse()
        .join("");
    }
  }
};
</script>

今回注目していただきたいのは次のコードです。

<button v-on:click="reverseMessage">Reverse Message</button>

button タグに v-on:click が追加されています。

v-on:click 以降には呼び出したい script のメソッド名を記載しています。

おわり

データ描画、v-bind、v-if、v-for、v-model、v-on:click と表現方法を学びました。

いまいち消化しきれていない方は、実際に手を動かして動作を確認することをおすすめします。

そこまで難しくない表現方法のため、

全て習得してしまい、自身のやりたいことに合わせて使い分けていきましょう。

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

【Vue.js】ref属性でマーキングしたコンポーネントを$refsで取得

生成されたコンポーネントを取得できる$refs

取得したいコンポーネントのタグ内に、ref属性を付与することでマーキングし、
実際に$refsでマーキングしたコンポーネントを取得できる。

注意点

もちろんすでにレンダリングされたコンポーネントを取得するので、VueライフサイクルにおけるbeforeMount以下のプロパティ内では使用することはできない。

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

コンポーネントを特定させる

<template>
  <v-container ref="hoge">
  ・・・
  </v-container>
</template>


<script>
    let hogeComponent = this.$refs.hoge
</script>

特定したい要素にref属性を付与して、実際の処理する時の対象指定として$refsを使用して特定させる。

配列の要素による、コンポーネントも特定させることができる

<template>
  <v-container>
    <v-row>
      <v-col v-for="item in items" ref="hoge">
      </v-col>
    </v-row>
  </v-container>
</template>


<script>
    let item1 = this.$refs.hoge[0]
</script>

ref属性の配列の要素の中でも、0番の要素だけを取得できる。

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

vue composition-api(1.0.0-beta.9) で css module 利用

useCSSModule を利用

<template>
  <div :class="style.title" @click="clickHandler()">{{ title }}</div>
</template>

<script lang="ts">
import { defineComponent, useCSSModule } from '@vue/composition-api';
export default defineComponent({
  props: {
    title: {
      type: String,
      required: true,
    },
  },
  setup: (_, { emit }) => {
    const clickHandler = () => {
      emit('clicked');
    };
    return { clickHandler, style: useCSSModule() };
  },
});
</script>

<style module lang="scss">
.title {
  color: #ffffff;
}
</style>

参考

https://github.com/vuejs/composition-api/pull/420#issuecomment-653874173

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

Vueにおけるフラッシュメッセージ表示のロジック

前提

ライブラリ・フレームワーク バージョン
Vue.js 2.6.11
Vuex 3.4.0

導入

Vueを使用して、フラッシュメッセージ(一時的に表示され、一定時間経つと自動的に消えるメッセージ)を作成するために、Vuexのストア機能を利用する。

なぜフラッシュメッセージの表示にVuexを利用するのか?

Vuexのメリットとは?

そもそもVuexを利用するメリットとしては、アプリケーション全体に関わる「状態」を、コンポーネントがバラバラに持たずに、ストアというデータ保管場所で一括で管理することにより、データ移動の複雑性が一気に解消される、という点にある。ちょいとむずいな〜。
どういうことかというと、以下の図をから説明してみる。。。
IMG_0257.JPG
例えば、Bというコンポーネントで何らかの状態が変化して、DコンポーネントのUIを変更しなければならない場合、Vuexを使用しない場合だと何が起こるのか?
Vueのコンポーネント間では、兄弟関係のデータの受け渡しはできず、必ず「親から子」「子から親」の方向でしか許されないため、図の上のような「工数が多く複雑なデータ移動」を強いられてしまう。。。

コンポーネントはお互いが疎結合(コンポーネント同士の結びつきが緩やかで、お互いが独立している状態)であるぼが望ましいので、このようにコンポーネント間のデータ移動に制限がある。しかしこの制限のおかげで、コンポーネント間の依存関係などあまり配慮せずに、コンポーネントの交換や改良を柔軟に行える。

しかし、Vuexをしようするとどうだろう?
Bコンポーネントに状態の変化が起きた場合、まずVuexへ状態の変化を報告し、更新する。そして、Dコンポーネントはその変化を察知し、Vuexの情報を参照し、取得する。これにより、上で示した「Bというコンポーネントで何らかの状態が変化して、DコンポーネントのUIを変更する」という問題を簡潔に解決できるようになる。

フラッシュメッセージとVuexの相性

ここまでVuexについて少し考えてきた。フラッシュメッセージというのは、ありとあらゆるコンポーネントで使用したい機能のひとつなはず。ログインに成功したとき、登録に成功したとき、編集に失敗したとき、などなど。各コンポーネントの「状態変化」に応じて、多数のコンポーネントからフラッシュメッセージ機能を呼びたいわけです。ここで普通に処理すると上の図のようなデータ移動に複雑性が生じてきます。
Vuexでフラッシュメッセージの内容をデータ管理することで、どこからでもスムーズにフラッシュメッセージを呼び出すことが可能になります。

フラッシュメッセージ実装の流れ

0 Vuexで状態管理するデータ内容

Vuexのステートにて状態管理するデータは「フラッシュメッセージにおけるメッセージ内容」とする。

1 ルートコンポーネントに「Message.vue」を登録する。

メッセージを表示するためだけのコンポーネント(Message.vue)をルートコンポーネントに登録する。これにより、ルートコンポーネントからフラッシュメッセージが表示されるようになる。以後、ルートコンポーネントを「App.vue」とする。

App.vue

Vue.js
<div class="container">
  <Message /> //追加
  <RouterView />
</div>
Vue.js
import Message from './components/Message.vue'
Vue.js
export default {
  components: {
    Message, 
    //省略
  },

2 Messeage.vueの作成

続いて、上で登録したMessage.vueの中身を作成していく。

Message.vue

Vue.js
<template>
  //message関数の返り値がtrueの場合、変数messageを表示させる
  <div class="message" v-show="message">
    {{ message }}
  </div>
</template>

<script>
  import { mapState } from 'vuex'

  export default {
    computed: {
      //メッセージストアのステートにセットされてある「メッセージ内容」を返す
      ...mapState({
        message: state => state.message.content
      })
    }
  }
</script>

ここで行っていることは、

1 メッセージストアのステートに値(メッセージ内容)がセットされているかどうかをcomputed内の関数にて確認し、セットされていない場合「空文字」を返し、セットされている場合は、メッセージ内容を返す。
2 メッセージ内容がセットされているときのみメッセージが表示される、つまりフラッシュメッセージが表示される。

というロジック。

3 ストアの作成

message.js

Vue.js
const state = {
  content: ''
}

const mutations = {
  setContent (state, { content, timeout }) {
    state.content = content

    if (typeof timeout === 'undefined') {
      timeout = 3000
    }

    setTimeout(() => (state.content = ''), timeout)
  }
}

export default {
  namespaced: true,
  state,
  mutations
}

外部(コンポーネント)からミューテーションを呼ぶ際、引数にメッセージ内容とメッセージが消えるまでの時間を指定することを可能にした。
これによってミューテーションが呼ばれた後、次の通りの手順を踏む
1 メッセージ内容がストアのステートにセットされる
2 指定の時間が経つと、セットしたメッセージ内容を自動的にクリアする

では次は、ミューテーションを呼ぶ側の実装をする。

4 メッセージを表示させたいタイミングに位置するコンポーネントに処理の記入

Vue.js
// メッセージ登録(例)
async submit() {
  const response = await axios.post(`XXXXXX`)
      if( response.status === CREATED) {
      //ここでメッセージストアのミューテーション呼ぶ
        this.$store.commit(`message/setContent`,{
          content: 'プロフィールの作成に成功しました!',
          timeout: 6000
        })
        this.$router.push(`YYYYYY`)
        return false
      }
   //以下省略
}

上のように何らかの処理が成功した後、ミューテーションを呼びフラッシュメッセージを表示させ、直後に画面遷移などする。以上が実装のステップである。

まとめ

フラッシュメッセージが表示されるまでの流れをまとめると以下の通りになる。

1 外部(コンポーネント)から、フラッシュメッセージを表示させたいタイミングで、メッセージ内容とフラッシュメッセージの表示時間を引数にミューテーション呼ぶ。

2 メッセージストアにて、1からもらった引数を受け取り、メッセージ内容をステートにセットする。(ステートにセットした値は、もう一つの引数である表示時間を向かえたとき自動的に消去される)

3 Message.vueのcomputedのmessage関数にて、メッセージストアのstateにメッセージ内容ががセットされているかチェックし、もし値がセットされている場合そのメッセージ内容を返す。メッセージ内容が返ってきた場合のみ、そのメッセージをMessage.vue内にて表示させる。

4 App.vue(ルートコンポーネン)にMessage.vueが直接登録されてあるので、Message.vueにてメッセージが表示されていれば、そのまま画面上にメッセージが表示される。

つまり上で書いた実装の流れと逆の流れで、データはフラッシュメッセージの表示まで走っていく。

以上です。ここまで読んでいただきありがとうございました。何か間違いや補足などありましたらご指摘いただけると幸いです。

参考

Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう (10) 写真投稿フォーム
Vuexを使ったローディング表示の実装方法

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

Vue.jsにおけるフラッシュメッセージ表示のロジック

前提

ライブラリ・フレームワーク バージョン
Vue.js 2.6.11
Vuex 3.4.0

導入

Vueを使用して、フラッシュメッセージ(一時的に表示され、一定時間経つと自動的に消えるメッセージ)を作成するために、Vuexのストア機能を利用する。

なぜフラッシュメッセージの表示にVuexを利用するのか?

Vuexのメリットとは?

そもそもVuexを利用するメリットとしては、アプリケーション全体に関わる「状態」を、コンポーネントがバラバラに持たずに、ストアというデータ保管場所で一括で管理することにより、データ移動の複雑性が一気に解消される、という点にある。ちょいとむずいな〜。
どういうことかというと、以下の図をから説明してみる。。。

例えば、Bというコンポーネントで何らかの状態が変化して、DコンポーネントのUIを変更しなければならない場合、Vuexを使用しない場合だと何が起こるのか?
Vueのコンポーネント間では、兄弟関係のデータの受け渡しはできず、必ず「親から子」「子から親」の方向でしか許されないため、図の上のような「工数が多く複雑なデータ移動」を強いられてしまう。。。

コンポーネントはお互いが疎結合(コンポーネント同士の結びつきが緩やかで、お互いが独立している状態)であるぼが望ましいので、このようにコンポーネント間のデータ移動に制限がある。しかしこの制限のおかげで、コンポーネント間の依存関係などあまり配慮せずに、コンポーネントの交換や改良を柔軟に行える。

しかし、Vuexをしようするとどうだろう?
Bコンポーネントに状態の変化が起きた場合、まずVuexへ状態の変化を報告し、更新する。そして、Dコンポーネントはその変化を察知し、Vuexの情報を参照し、取得する。これにより、上で示した「Bというコンポーネントで何らかの状態が変化して、DコンポーネントのUIを変更する」という問題を簡潔に解決できるようになる。

フラッシュメッセージとVuexの相性

ここまでVuexについて少し考えてきた。フラッシュメッセージというのは、ありとあらゆるコンポーネントで使用したい機能のひとつなはず。ログインに成功したとき、登録に成功したとき、編集に失敗したとき、などなど。各コンポーネントの「状態変化」に応じて、多数のコンポーネントからフラッシュメッセージ機能を呼びたいわけです。ここで普通に処理すると上の図のようなデータ移動に複雑性が生じてきます。
Vuexでフラッシュメッセージの内容をデータ管理することで、どこからでもスムーズにフラッシュメッセージを呼び出すことが可能になります。

フラッシュメッセージ実装の流れ

0 Vuexで状態管理するデータ内容

Vuexのステートにて状態管理するデータは「フラッシュメッセージにおけるメッセージ内容」とする。

1 ルートコンポーネントに「Message.vue」を登録する。

メッセージを表示するためだけのコンポーネント(Message.vue)をルートコンポーネントに登録する。これにより、ルートコンポーネントからフラッシュメッセージが表示されるようになる。以後、ルートコンポーネントを「App.vue」とする。

App.vue

Vue.js
<div class="container">
  <Message /> //追加
  <RouterView />
</div>
Vue.js
import Message from './components/Message.vue'
Vue.js
export default {
  components: {
    Message, 
    //省略
  },

2 Messeage.vueの作成

続いて、上で登録したMessage.vueの中身を作成していく。

Message.vue

Vue.js
<template>
  //message関数の返り値がtrueの場合、変数messageを表示させる
  <div class="message" v-show="message">
    {{ message }}
  </div>
</template>

<script>
  import { mapState } from 'vuex'

  export default {
    computed: {
      //メッセージストアのステートにセットされてある「メッセージ内容」を返す
      ...mapState({
        message: state => state.message.content
      })
    }
  }
</script>

ここで行っていることは、

1 メッセージストアのステートに値(メッセージ内容)がセットされているかどうかをcomputed内の関数にて確認し、セットされていない場合「空文字」を返し、セットされている場合は、メッセージ内容を返す。
2 メッセージ内容がセットされているときのみメッセージが表示される、つまりフラッシュメッセージが表示される。

というロジック。

3 ストアの作成

message.js

Vue.js
const state = {
  content: ''
}

const mutations = {
  setContent (state, { content, timeout }) {
    state.content = content

    if (typeof timeout === 'undefined') {
      timeout = 3000
    }

    setTimeout(() => (state.content = ''), timeout)
  }
}

export default {
  namespaced: true,
  state,
  mutations
}

外部(コンポーネント)からミューテーションを呼ぶ際、引数にメッセージ内容とメッセージが消えるまでの時間を指定することを可能にした。
これによってミューテーションが呼ばれた後、次の通りの手順を踏む
1 メッセージ内容がストアのステートにセットされる
2 指定の時間が経つと、セットしたメッセージ内容を自動的にクリアする

では次は、ミューテーションを呼ぶ側の実装をする。

4 メッセージを表示させたいタイミングに位置するコンポーネントに処理の記入

Vue.js
// メッセージ登録(例)
async submit() {
  const response = await axios.post(`XXXXXX`)
      if( response.status === CREATED) {
      //ここでメッセージストアのミューテーション呼ぶ
        this.$store.commit(`message/setContent`,{
          content: 'プロフィールの作成に成功しました!',
          timeout: 6000
        })
        this.$router.push(`YYYYYY`)
        return false
      }
   //以下省略
}

上のように何らかの処理が成功した後、ミューテーションを呼びフラッシュメッセージを表示させ、直後に画面遷移などする。以上が実装のステップである。

まとめ

フラッシュメッセージが表示されるまでの流れをまとめると以下の通りになる。

1 外部(コンポーネント)から、フラッシュメッセージを表示させたいタイミングで、メッセージ内容とフラッシュメッセージの表示時間を引数にミューテーション呼ぶ。

2 メッセージストアにて、1からもらった引数を受け取り、メッセージ内容をステートにセットする。(ステートにセットした値は、もう一つの引数である表示時間を向かえたとき自動的に消去される)

3 Message.vueのcomputedのmessage関数にて、メッセージストアのstateにメッセージ内容ががセットされているかチェックし、もし値がセットされている場合そのメッセージ内容を返す。メッセージ内容が返ってきた場合のみ、そのメッセージをMessage.vue内にて表示させる。

4 App.vue(ルートコンポーネン)にMessage.vueが直接登録されてあるので、Message.vueにてメッセージが表示されていれば、そのまま画面上にメッセージが表示される。

つまり上で書いた実装の流れと逆の流れで、データはフラッシュメッセージの表示まで走っていく。

以上です。ここまで読んでいただきありがとうございました。何か間違いや補足などありましたらご指摘いただけると幸いです。

参考

Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう (10) 写真投稿フォーム
Vuexを使ったローディング表示の実装方法

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

マークアップエンジニアがNuxt.js案件で必須な知識【5選】

こんにちは、現在フリーのマークアップエンジニアとして活動しているしょーごと申します。
最近はYouTubeなどでも発信しています。

普段はWordPress構築や静的サイトコーディングをメインとしているのですが、
最近機会に恵まれまして、Nuxt.jsのマークアップ部分(ロジック以外)を担当しております。

結果的に大苦戦してベテランの方に大迷惑をかけてしまったので、
マークアップエンジニアやデザイナーがNuxt.jsのマークアップ部分をアサインされた場合に必要な知識をまとめました。

Nuxt.jsはweb制作業界にも少しづつ浸透してきているので、マークアップエンジニアやデザイナーさんもいずれ触る期間があるかもしれない点で、同業の方々に参考にして頂けたらと思います。

俺の屍を超えていけ

Nuxtってなんなの?

Vue.js単体では開発に不足している機能が多いです。なので従来はプラグインやライブラリを導入していましたが、Nuxtは色んな機能が最初から盛り込まれているフレームワークになります。
Vue.js+ライブラリの集まりみたいなものです。

Nuxt.jsのマークアップ部分とは?

Nuxt,jsはご存知の通り、単一ファイルコンポーネントと呼ばれています。

HTML,CSS,JSが一つのファイルにまとまっています。

以下コードは超適当なので、scriptにロジックがかいてあるんだとだけ認識頂けたら?

default.vue
<template>
  <div>
    <div class="posts overflow-scroll mb-24">
      <post v-for="(post,index) in posts" :key="index" :post="post" />
    </div>
      <el-upload v-if="!imageUrl" action :show-file-list="false" :http-request="uploadFile">
        <el-button size="small" type="primary">Click to upload</el-button>
      </el-upload>
      <el-input type="textarea" :rows="8" placeholder="Please input" class="mt-8" v-model="text"></el-input>
  </div>
</template>
<script>
import Post from "~/components/Post.vue";
import { db, firebase } from "~/plugins/firebase";
export default {
  components: {
    Post,
  },

  data() {
    return {
      posts: [],
      imageUrl: null,
      text: null,
    };
  },
  methods: {
    async post() {
      await db.collection("posts").add({
        text: this.text,
        image: this.imageUrl,
        createdAt: new Date().getTime(),
      });
      this.text = null;
      this.imageUrl = null;
      window.alert("保存されました");
    },
  },
  mounted() {
    db.collection("posts").onSnapshot((snapshot) => {
      snapshot.docChanges().forEach((change) => {
        const doc = change.doc;
        if (change.type === "added") {
          this.posts.unshift({ id: doc.id, ...doc.data() });
        }
      });
    });
  },
};
</script>
<style lang="scss" scoped>
input {
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
}
.posts {
  text-align: center;
}

ここのscript以外が作業範囲になります。
つまり、チーム開発が前提です。

厳密に言えば、

1.HTML部分のコーディング
2.Scoped CSS、グローバルCSSの記述と設計(Sassが標準)
3.コンポーネント分け(ここだけscript部分)
4.状態管理でのclass出し分け(v-forやv-bind,propsあたり)
5.CSSフレームワークの利用と共存(Tailwindやbulma、Element-uiやBuefyなど)

このあたりが業務範囲になります。
その他はプログラマーさんに担当していただきます。

プログラマー→フロントエンド、フルスタックエンジニア
マークアップエンジニア→コーダー、デザイナー

では、これらをふまえてNuxt.jsのマークアップ部分を担当する上で何を知っておくといいのか解説します。

①Nuxtのコンポーネント、レイアウト、ページルーティング

やはりここが一番重要になるかと思います。
以下公式の画像になります。
スクリーンショット 2020-08-19 17.21.42.jpg

公式サイト:ビューについて

Nuxtでは一般的にビューの表示は

1.レイアウト(全ページ読み込まれる)
2.ページ
3.コンポーネント

の3つに別れます。

レイアウトの役割

レイアウトは各ページでの共通要素を書きます。
大抵はheaderとfooterコンポーネントを呼びこむぐらいだと思います。

default.vue
<template>
  <div>
    <app-header />
    <nuxt />
    <app-footer />
  </div>
</template>

<nuxt />という部分に、各ページの内容が入ってきます。

ページの役割

そのページ専用の内容を書いていきます、
直接HTMLを書いたり、コンポーネントを呼び込んで構築していきます。
レイアウトでheaderとfooterは呼び込み済みなので、それ以外の部分になります。

またNuxtではルーティングがすでに設定してあるため。例えばusersページを作る際には、

スクリーンショット 2020-08-19 17.56.32.jpg

これでlocalhost:3000/usersでその配下のindex.vueを表示させることができます。

pagesディレクトリ以下がそのままページのディレクトリ構成を示しています。

コンポーネントの役割

header,footer,sidebar,navigationなどダントツでファイル数が多くなります。
基本的に共通化できそうな塊はだいたいコンポーネント化していきます。
レイアウトやページで読み込んで表示させていきます。
大きめのコンポーネントの場合、コンポーネントの中でコンポーネントを読み込むなどの状況もよくあります。

私のブログを例にしてみます。

赤枠:レイアウト
緑枠:ページ
青枠:コンポーネント

screencapture-shogo-log-2020-08-19-17_28_21.png

screencapture-shogo-log-2020-08-19-17_28_21 (1).png

弊ブログ:しょーごログより

コンポーネントの読み込みはimportとexport defaultをセットで使います。

userコンポーネントを読み込む場合

index.vue
<template>
  <div class="users">
    <user />
    <user />
  </div>
</template>

<script>
import User from "~/components/User.vue";
export default {
  components: {
    User,
  },
};
</script>

コンポーネント間でのメソッドや状態の受け渡しなどもあるので、プログラマーと相談しつつコンポーネント分けの粒度を決めるといいかなと思います。

②Scoped CSSとグローバルCSS設計

Scoped CSSについて

Scoped CSSは、scopedを記入するだけで使えます。その単一ファイル内にしかCSSのスコープが及ばないため、グローバル汚染がなくよく
「CSS is too fragileからの脱却だ!」的に叫ばれますが、しかしやはり銀の弾丸はなく、h2やsectipnなどのセレクタに直接指定すると普通に子コンポーネントを汚染します。
スクリーンショット 2020-08-19 21.46.07.jpg
スクリーンショット 2020-08-19 21.47.24.jpg

参考サイト:ICS Media

このようにカスタムデータ属性により影響範囲を限定的にしています

まあ普通にBEMとか使っていれば問題ないです。

Scoped CSSにおけるクラス設計

詳しくは以下の記事に譲りますが、Scoped CSSでも暴虐無人プレイができるわけではないです。
ただ、普通にBEM的にクラス名を書いていけば、バッティングの可能性は相当低くなるので、

Web制作にNuxt.jsが有用である理由の一つとなっております。

Scoped CSSにおけるCSS設計手法

グローバルCSS

全体に適用したいCSSはassets以下にcssファイルを置いて、それをnuxt.config.jsで読み込むのですが、とりあえず「Scoped CSS」と「グローバルCSS」に分かれているらしいぐらいの認識で大丈夫です。
reset.scssとか_variables.scss,_mixin.scssなど全体で使いそうなものを定義していきます。

③状態変化でクラスの出し分け

v-bind,props

意外にここも知っておいたほうがいいです。
例えばよく「トップページのみヘッダーのデザインが違う?」ということはよくあります。
しかしレイアウトでheaderコンポーネントを読み込んでいるため、トップページのみ変更するのは難しく感じます。

そこでpropsとv-bindが出てきます。

手順としては

1.トップページ専用のレイアウトを準備し、利用
2.topであることをBooleanで管理し、trueのときにheaderにクラス付与

となります。

トップページ専用のレイアウトをtop.vue、trueを渡す先のheaderコンポーネントははheader.vueとし、トップページの表示はindex.vueが行います。

まずはtop.vueで:is-top="true"とすることでトップであることをheaderコンポーネントに伝えます。

top.vue
<template>
  <div>
    <header :is-top="true" />
    <Nuxt />
    <footer />
  </div>
</template>

<script>
import Header from '~/components/header-nav'
import Footer from '~/components/footer-nav'

export default {
  components: {
    Header,
    Footer
  }
}
</script>

その後、index.vueではレイアウトとしてtop.vueを使うと宣言します。

index.vue
layout: 'top',

headerコンポーネントでpropsによりtrueを受け取り、v-vindでtrueのときにclassを付与します。

header.vue
<template>
  <header :class="{ 'top-header' : isTop === true }">
  </header>
</template>
<script>
  props: {
    isTop: {
      type: Boolean
    }
  }
</script>

これにより、トップページにのみtop-headerというクラスが付与できるようになります。

他にもv-onやv-forについても知っておいたほうがいいです。
v-onはモーダルの表示、v-forは<li v-for="(item, index) in items">などでindexを取り出しループ内で動的にクラス名を付与したいときに、役立ちます。
ここでは詳しくは割愛します。

④npm関連

直接Nuxtとは関係しませんが、npmを普段より利用しているかも重要です。
マークアップ側が自分でパッケージを入れることはほとんどないと思いますが、必須知識です。

npm install

チーム開発ではよくパーケージが新しくインストールされていたりします。
その際に、「package.jsonが更新されてる!!ということはnpm iすればいいんだ!!」と知っていればいいのですが、知らないとエラー画面に飛ばされます笑

npm run dev

Nuxtのサーバーを起動するnpm scriptsです。
ファイルを保存したら、画面を自動リロードしてコンパイルしてくれます。

nvm

Node.jsのバージョン管理ツールです。
開発開始前にnode.jsのバージョンはチームメンバーで合わせておくべきです
npmで入れるモジュールが、Nodeのバージョンに依存するため、チームメンバーのグローバルNodeのバージョンが違うと、地獄と化します。

⑤Git

これもNuxtから離れますが、チーム開発ではかなり重要です。
Nuxtを扱うような案件では十中八九必要でしょう。
いやテーム開発でGit運用とか常識やろと声が聞こえそうですが、1人Gitしか経験なく、マークアップ案件でGithub flowでのチーム開発とかほとんどないので自分は大苦戦しました...
diffが見やすいsourcetreeで大丈夫だと思います。

最低限抑えておくべきこと

  • github flow(機能やページ毎にfeatureブランチを切って運用するぐらい)
  • プル→ステージング→コミット→プッシュ→プルリクの流れがわかる
  • コンフリクト対応
  • スタッシュを知っておく(割と変更内容を退避させるため)

ぐらいかなと思います。コンフリクトの解消など自分はパニクってしまいレビュアー先輩エンジニアの方によしなにしていただきマージしてもらいましたが、これは迷惑かけたのであまりよくないと思いました。

Gitでの開発流れ

1.最新origin/masterからローカルmasterにpull(この際にpackage.jsonが更新されていたら、npm iしてパッケージを入れておく)
2.masterからfeatureブランチを切り作業開始
3.作業が完了したらコミットしプッシュする
4.プルリクを出し、レビュアーにレビューを受ける、問題なければマージされる、問題あれば修正し再度コミットプッシュする
5.マージされたらmasterにチャックアウトし、必ずプルし最新のmasterにする。
6.そして次のfeatureブランチを切り、作業開始

少人数の開発&トラブルがなければこんな感じだと思います。

Nuxt.jsでのマークアップ案件は増えるのか

NuxtはfirebaseやヘッドレスCMSと組み合わせてJamstackで利用されているイメージですが、ロジック部分に集中したい人は一定層いると考えています。
その際に、Nuxtの構造を理解しているマークアップエンジニアにスタイリングを外注できたら楽そうなので、
需要としてはポツポツ発生していそうだと思います。

Nuxtオンリーでの静的サイト制作については、未知数です。
Web制作で使われるならVueでのアニメーションの知識が必要になるのと、枯れているjQueryの資産が偉大なので、すぐに広まるのは考えにくいです。
しかし保守性の面でコンポーネントやScoped CSSが大変魅力的なので、大規模サイト制作で真価を発揮するかなと思っています。

事前に学習で使用していた教材

Vue.js&Nuxt.js超入門
Gitが、おもしろいほどわかる基本の使い方33

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

Vue CLI slotの使い方

slotの使い方について

Vue CLIでの使用方法がイメージし辛かったが、分かってきたことのまとめ

【使用方法】

子コンポーネントでname属性付きのslotタグを設定。
親コンポーネントで呼び出すコンポーネントを呼び出す。
※この時普段は</MypageCardForm>と子コンポーネントを親コンポーネントの記述内に入れるだけだが、
slotを使用する場合は

<MypageCardForm>
  <template #指定したname属性名>
    slotで呼び出す記述
  </template>
</MypageCardForm>

と記述し、呼び出す。

以下詳細

MypageCardForm.vue (子コンポーネント)

<template>
  <v-card
    max-width="344"
    class="mx-auto"
  >
    <v-list-item>
      <v-list-item-avatar color="grey"></v-list-item-avatar>
      <v-list-item-content>
        <v-list-item-title class="headline">Our Changing Planet</v-list-item-title>
        <v-list-item-subtitle>by Kurt Wagner</v-list-item-subtitle>
        <slot name='slotTest'></slot>
      </v-list-item-content>
    </v-list-item>

    <v-img
      src="https://cdn.vuetifyjs.com/images/cards/mountain.jpg"
      height="194"
    ></v-img>

    <v-card-text>
      Visit ten places on our planet that are undergoing the biggest changes today.
    </v-card-text>

    <v-card-actions>
      <v-btn
        text
        color="deep-purple accent-4"
      >
        Read
      </v-btn>
      <v-btn
        text
        color="deep-purple accent-4"
      >
        Bookmark
      </v-btn>
      <v-spacer></v-spacer>
      <v-btn icon>
        <v-icon>mdi-heart</v-icon>
      </v-btn>
      <v-btn icon>
        <v-icon>mdi-share-variant</v-icon>
      </v-btn>
    </v-card-actions>
  </v-card>
</template>

このファイル内で

   <slot name='slotTest'></slot>

を設定する。
ポイントはname='slotTest'
ここでname指定したものを親コンポーネントで

<template #slotTest></template>

で呼び出す。

Login.vue (親コンポーネント)

<template>
  <v-container>
    <v-row justify="center" align="center">
      <v-col cols="12">
      <MypageCardForm>
          <template #slotTest>
              <v-btn>ログインページのカード</v-btn>
        </template>
      </MypageCardForm>
      <LoginFormTD/>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import LoginFormTD from '../components/LoginFormTD'
import MypageCardForm from '../components/MypageCardForm'

export default{
    name:'Login',//ここの名前は任意設定

    components:{
        LoginFormTD,//ここの記載をtemplateで呼び出す際に使う
        MypageCardForm,
    },
}

</script>

呼び出した結果
スクリーンショット 2020-08-20 6.53.11.png

 <MypageCardForm>
          <template #slotTest>
              <v-btn>ログインページのカード</v-btn>
        </template>
      </MypageCardForm>

このファイルでの呼び出しでは「ログインページのカード」というボタン部分で使用。

次に別のファイルでも同じMypageCardForm.vueを呼び出し別記述でslotを呼び出してみる。

PostList.vue (親コンポーネント)

<template>
  <v-container>
    <v-row justify="center" align="center">
      <v-col cols="8">
          <PostCardForm/>
          <PostCardForm/>
          <PostCardForm/>
          <PostCardForm/>
      </v-col>
      <v-col cols='4'>
      <MypageCardForm>
        <template #slotTest>
          <h3>ポストページのカード</h3>
        </template>
      </MypageCardForm>

      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import PostCardForm from '../components/PostCardForm'
import MypageCardForm from '../components/MypageCardForm'

export default{
    name:'Top',//ここの名前は任意設定

    components:{
        PostCardForm,//ここの記載をtemplateで呼び出す際に使う
        MypageCardForm,//ここの記載をtemplateで呼び出す際に使う

    },
}



</script>

呼び出した結果
スクリーンショット 2020-08-20 6.53.04.png

ここでは

  <MypageCardForm>
        <template #slotTest>
          <h3>ポストページのカード</h3>
        </template>
      </MypageCardForm>

の部分で使用。
h3タグをslot部分に入れている。
先ほどボタンタグで使用されていた「ログインページのカード」の記述ではなくh3のポストページのカードがしようされている。

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

vue.js <slot>タグで親からHTMLを受け取る

親からデータを受け取る方法として、<slot>タグがある。
親の<template>とその変数を受け取ることができる。

子コンポーネントに<slot>を配置する

<slot></slot>をテンプレート部に配置する。
これが親から受け取ったデータの受け口になる。

child.vue
<template>
<div>
    <slot></slot>
</div>
</template>

親コンポーネントで送るデータを記述する

子コンポーネントを呼び出すタグの中に送りたいHTMLを記述する。

parent.vue
<template>
<div>
    <Child>
      <!-- 子に送られる内容 -->
      <h1>text</h1>
      <h1>{{number}}</h1>
    </Child>
</div>
</template>

<script>
import Child from "./components/Child"

export default{
  data() {
    return {
      number: 10,
    };
  },
  components: {
    Child
  },
};
</script>

表示してみる。
image.png
子コンポーネントの<slot>部が親で記述した内容に置き換わり、表示される。

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

Vue CLI propsの使い方

propsについて

具体的な使用方法がイメージしづらかったため、学習内容の備忘録
参考記事:https://mebee.info/2020/02/24/post-5009/

結果
スクリーンショット 2020-08-20 6.11.21.png

サイトのトップページのテキスト部分上下をpropsにて変更している(使用しているのは同一コンポーネント)

App.vue

<template>
  <v-app>
   <Header/>
    <router-view/>
   <!-- <Top/>
   <Index/>
   <TopSlide/> -->
   <Footer/>
  </v-app>
</template>
<style scoped>
  v-container{
    padding-bottom: 0;
  }
</style>

<script>
import Header from './views/Header';
import Footer from './views/Footer';
// import Top from './views/Top';
// import Index from './views/Index';
// import TopSlide from './views/TopSlide';

export default {
  name: 'App',

  components: {
    Header,
    Footer,
    // Top,
    // Index,
    // TopSlide,
  },

  data: () => ({
    //
  }),
};
</script>

App.vueでルーターリンクを設定

TopPage.vue(親コンポーネント:このコンポーネントで今回使用するpropsを呼び出す)

<template>
  <v-container>
    <v-row justify="center" align="center">
      <v-col cols="12">
      <TopForm/>
      <IndexForm
       title= "本サイトについて"
    subTitle= "FEATURE"
    text1= "外出自粛が広く実施される昨今"
    text2= "「Life Flower」は、改めて家庭で花を楽しむ体験を提案します"
    text3= " 皆さんの生活に彩りを添える花を1輪飾ってみませんか。"
    text4= "あなたの街の素敵なお花屋さんと花々を楽しむ体験を"
    text5= "私たちは提案します。"



      />
      <TopSlideForm/>
      <IndexForm
        :title= "test[0].title"
    :subTitle= "test[0].subTitle"
    :text1= "test[0].text1"
    :text2= "test[0].text2"
    :text3= "test[0].text3"
    :text4= "test[0].text4"
    :text5= "test[0].text5"
      />
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import TopForm from '../components/TopForm'
import IndexForm from '../components/IndexForm'
import TopSlideForm from '../components/TopSlideForm'

export default{
    name:'TopSlide',//ここの名前は任意設定

    components:{
        TopForm,//ここの記載をtemplateで呼び出す際に使う
        IndexForm,//ここの記載をtemplateで呼び出す際に使う
        TopSlideForm,//ここの記載をtemplateで呼び出す際に使う

    },
  data: () => {
    return {      
      test: [
        { title: '使い方',
         subTitle: 'あなたのお気に入りのお店をみつけよう',
          text1: '現在地や任意の場所を指定して',
          text2: '検索をスタート', 
          text3: 'あなたの街の素敵なお店が見つかります。', 
          text4: '気に入ったお店をフォローして', 
          text5: '毎日投稿をチェックしましょう', },
          //参考サイト:https://mebee.info/2020/02/24/post-5009/
      ],
    };
  },    
}

</script>

TopPage.vueで子コンポーネントであるIndexForm.vueを呼び出す。
呼び出す際にpropsを使用し子コンポーネント で{{}}マスタッシュによってpropsを指定している部分を設定する。
importしてコンポーネントを使用できるようにし、以下の通り呼び出す。
呼び出し方法は複数ある。
今回はIndexFormを2つ呼び出している。
1つはタグに直接指定する方法
2つはscriptタグ内のdataにバインド(bind:)して呼び出している。

IndexForm.vue

<template>
  <v-container>
    <v-row>
        <v-col class='feature'>
        <h3>{{title}}</h3>
        <span>{{subTitle}}</span>
        <div>{{text1}}<br>
        {{text2}}<br>
       {{text3}}<br>
        {{text4}}<br>
       {{text5}}</div>
        </v-col>
    </v-row>



  </v-container>
</template>

<style scoped>
    .feature{
        text-align: center;
    }
</style>

<script>
export default {
   name: "IndexForm",
  props: {
    title: String,
    subTitle: String,
    text1: String,
    text2: String,
    text3: String,
    text4: String,
    text5: String,
  },

}
</script>

propsを設定する箇所に{{}}マスタッシュを使用し、scriptタグ内でpropsを設定。
親コンポーネントでpropsで設定した内容を呼び出し使用できるようになる。

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

【Vue.js】令和のメン募は動画でやろうぜ!

はじめに

music_black_metal_band_corps_paint.png

暑いですね!絶賛夏バテ中です。私は普段エレクトリックベースを弾いています。
大学を卒業してしばらくの間バンド活動は行っていなかったのですが、転職を機にバンドメンバーとシェアハウスに住み「ここから俺のサクセスストーリーが始まるんや!!」と鼻息荒くしてバンドを始めました。

が、そのバンドは結成半年、一回のライブで解散をしました。(チーン)

メン募とは

メン(バー)(集)の略語です。
私が中学生の頃は「バンドやろうぜ!!」「GIGS」という二大バンド雑誌がありまして、巻末にはメンバー募集のコーナーがありました。

当方Vo.全パート募集
作詞やります。完全プロ志向の方のみでよろしくお願いします
東京ドームの舞台に立つまで私の夢は続きます

上の内容を解説するとインフラ、フロントエンド、ネットワークに精通したエンジニア募集。
当方プログラミング未経験、打倒GAFAを目指しています
くらいの感じです。
かなり極端な例ですが、近いものは結構見ましたね。

さてさて、YouTube、Instagramなどの動画メディア全盛の令和二年、前述のような文章だけではなかなかメンバーは集まりにくいもの。
これからの楽器奏者の名刺がわりにパート別の演奏動画を投稿することでメンバー募集のお手伝いに使えるサイトを考案しました。
覚えたてのVue.jsでそのメリットを活かしきれずの制作でうまくいっていない部分が多々ありますが、今後の自分を見つめ直す上で公開いたします。
  

実装

メモ:デプロイが未完了です


最低限の実装ですがGuitar,Bass,Drumそれぞれのタブでジャンル分けして演奏動画を一覧して、再生をします。
スクリーンショット 2020-08-20 3.54.08.png

コードは、こちらの記事を参考にしました。
【Vue.js】爆速でSPAを作る
本来は弾いてみた系の動画を貼りたかったのですが、素材を用意することができなくて、大物ミュージシャンの演奏動画をお借りしました。
元々の目指していたところはVue.jsを使用したアプリ制作を通してSPA(SinglePageApplication)に対する理解を深めることでした。
ですが、Vue.jsのコンポーネント指向などが理解はできるものの実装にうまく落とし込めませんでした。
ですので、主にうまくいかなかった点をピックアップしていきます。

コンポーネント指向を活かせなかった

コンポーネントの基本
例えばなのですが、各コンポーネントはGASで用意したデータベースのURLと背景画像以外は全て同じです。
なのでVue.jsのメリットであるコンポーネントの再利用が生かせるはずだったのですがいまいち理解できず、実装をしてみたものの画面に表示されずでした。
データベースと画像のURLだけが違ったコンポーネントが3つ存在します。そして初期表示がかなり重い時があります。

const { default: Axios } = require("axios");
<template>
 <div class="container">
  <div class="columns is-multiline ">
     <div v-for='data in datas' :key='data.name' class="column is-4">
      <infinite-loading @infinite="infiniteHandler" spinner="spiral">
       <div slot="spinner"></div>
       <div slot="no-more"></div>
       <div slot="no-results"></div>
      </infinite-loading>
      <iframe id="ytplayer" type="text/html" width="320" height="180" :src="data.url" frameborder="0"></iframe>
      <p>name: {{ data.name }} </p>
      <p>title: {{ data.title }} </p>
      <div class="buttons has-addons is-centered">
         <button class="button is-success">Like</button>
         <button class="button is-primary">Stock</button>
         <button class="button is-info">Share</button>
      </div>
     </div>
  </div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      datas: []
    }
  },
  computed: {
    player () {
      return this.$refs.youtube.player
    }
  },
  mounted: function () {
    this.getData()
  },
  methods: {
    infiniteHandler ($state) {
      if (this.end > this.datas.length) {
      // 表示するデータが無くなった場合
        $state.complete()
      } else {
      // 表示するデータがある場合
        this.getData()
        $state.loaded()
      }
    },
    async playVideo () {
      await this.player.playVideo()
    },
    getData: async function (event) {
      let url = 'SPREADSHEET_URL'
      this.axios.get(url)
        .then(response => (this.datas = response.data))
    }
  }
}
</script>
<style lang="scss">
body {
  background-image: url("../assets/guitar.jpg");
  background-color:rgba(255,255,255,0.8);
  background-blend-mode:lighten;
  background-position: center center;
  background-repeat: no-repeat;
  background-attachment: fixed;
  background-size: cover;
}
</style>

無限スクロールがうまくいかなかった

Vue.jsのライブラリで無限スクロールのライブラリを使用しました。
本来は読み込み中はスピナーが回転して見栄えが良いのですがそのスピナーがずっと回り続けていたので該当の部分は表示されないようにしました。
無限スクロールもうまく動いていません。

      <infinite-loading @infinite="infiniteHandler" spinner="spiral">
       <div slot="spinner"></div>
       <div slot="no-more"></div>
       <div slot="no-results"></div>
      </infinite-loading>

こちらの記事をよく読み再チャレンジしたいですね。
Vue.jsでお手軽実装!無限スクロールを実装する2つの方法

BULMAが便利だった

良かった点もあげておきます。
下記のコードだけでいい感じのボタンを実装してくれました。
スクリーンショット 2020-08-20 3.54.22.png

BULMAは今後も使いたいですね。
現在はダミーなので中身も作らねばなりませんが。

      <div class="buttons has-addons is-centered">
         <button class="button is-success">Like</button>
         <button class="button is-primary">Stock</button>
         <button class="button is-info">Share</button>
      </div>

まとめ

今回の課題は夏にちなんだ自由課題というもので、当初はガリガリ君のアプリを作ろうとしたんですが諸事情で企画が
突然変わってしまいました。
夏には関係ないし、所々雑なところがありますが、出てきた物が全てですのでこの記事を時折読み返して今後に活かしたいと思います。
本来やりたかったことができなくなってモチベーションも下がり気味でこの企画自体無理やり捻り出したものでしたが作ってる途中で楽しくなったのでやってみないとわからないとはこのことなんだなと思いました。

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

Vuetifyのv-text-fieldでtype="number"の時に、表示されている値を変更する

こんにちは!
長らくプレーンJavaScriptガリゴリマンでしたが、最近仕事の関係でVue.jsを触り始めた和尚でございます:star2:

それでは早速見ていきましょう!

環境

  • Vue.js v2.6.11
  • Vuex v3.4.0
  • Vuetify v2.2.11

注意

※かなりパワープレイです。参考程度に見てください。

本日の議題

ある昼下がりのこと

Aさん「数値しか入れられないフォームに文字が入っちゃうんだけど、どうにかならない?あと、最大値が999のはずなのに、キーボードで入力すると1000以上入っちゃうからついでに直して...」

和尚「まじっすかー。ちょっと調べてみまーす!」

...

今回の要件における Input type="number" の困った仕様

  • 最大値と最小値はキーボード入力で超えることが可能
  • safariは文字列を入力すると自動削除されない
  • chromeは指数表現のe・Eが入力できる

解決しよう!

それではAさんの要件に沿って修正していきましょう!
修正前のサンプルコンポーネントとストアはこちらになります!

Count.vue
<template>
  <div>
    <v-text-field
      ref="count"
      label="カウント"
      type="number"
      v-model="count"
      max="999"
      min="1"
    />
  </div>
</template>

<script>
export default {
  computed: {
    count: {
      get() {
        return this.$store.getters['count'];
      },
      set(num) {
        this.$store.commit('changeCount', num);
      }
    }
  }
}
</script>
index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 1
  },
  getters: {
    count: (state) => state.count
  },
  mutations: {
    changeCount(state, num) {
      if (num) state.count = num;
    }
  },
  actions: {}
})

①数値のみが入るようにする

テキストフィールドのvalueに文字列が入力された場合は空文字が返却されます。なのでsafariで文字列が入力されたままでもストアで管理している値に文字列が入ることはありません。(空文字を除く)

ということは、テキストフィールドの中にはvalueとは違う値が表示されているということです。

まずはテキストフィールドに数値のみしか入らないように修正していきましょう!
算術プロパティに記述した、countのsetterの中を少し弄ってテキストフィールドに現在のvalueの値が表示されるようにしましょう!

Count.vue
<script>
export default {
  computed: {
    count: {
      get() {
        return this.$store.getters['count'];
      },
      set(num) {
        num = parseInt(num)
        if (Number.isNaN(num) || num < 1) {
          num = 1;
        }

        this.$refs.count.lazyValue = num;
        this.$store.commit('changeCount', num);
      }
    }
  }
}
</script>
  1. valueをparseIntを使用して文字列から数値にする (Input type="number"の値はString型です)
  2. 数値ではない場合にnumに1を代入
  3. this.$refs.count.lazyValueに値を入れる (v-text-fieldの表示している値を格納しているプロパティ)
  4. ストアの値も変更する

②最大値・最小値の制御

ここまで来たらあとは簡単!
最大値と最小値の制御を入れてあげましょう!

Count.vue
<script>
export default {
  computed: {
    count: {
      get() {
        return this.$store.getters['count'];
      },
      set(num) {
        const min = Number(this.$refs.count.$attrs.min);
        const max = Number(this.$refs.count.$attrs.max);

        num = parseInt(num)
        if (Number.isNaN(num) || num < min) {
          num = min;
        } else if (num > max) {
          num = max;
        }

        this.$refs.count.lazyValue = num
        this.$store.commit('changeCount', num);
      }
    }
  }
}
</script>
  1. this.$refs.countの下に$attrsという属性値が格納されたプロパティがあるので、そこから最大値と最小値を取り出します。
  2. 文字もしくは最小値以下だった場合は、最小値を代入
  3. 最大値を超えた値が入力された場合は最大値を代入

完成!!

これでchromeでもsafariでも問題なく、要件を満たすことができました!

this.$refs.count.lazyValueを見つけるまでは、ネットで似たようなことをしている記事を探しては試していました。しかし完璧に要件を満たせる方法がなくて諦めかけてたところ、何となくconsole.log(this.$refs.count)を書いてプロパティみてたらそれっぽい値を発見しました!そして、うまく値を書き換えることに成功笑

結構稀な対応かとは思いますが、同様のパターンを実装されたい方は是非参考にしていただけると幸いです。
もっと良い案があればコメントください!

ではでは!

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