20210111のvue.jsに関する記事は9件です。

【速習】vue-property-decorator

これを書いた趣旨

実務でvue-property-decoratorを使ってVueをTypeScriptで記述する必要があり、ざっと理解した内容を個人用メモとしてまとめました。間違っている点があれば、ご指摘頂けますと幸いです。

vue-property-decoratorの前に、Vue Class Componentから

Vue Property DecoratorのREADMEを読むと、最初にこう書かれています。

This library fully depends on vue-class-component, so please read its README before using this library.(このライブラリは vue-class-component に完全に依存しています。)

つまり、vue-property-decoratorを使う前にvue-class-componentが使える状態にしておけよ、ということなので、公式VueからVue Class Componentを確認します。

Vue Class Componentとは

  • Vue Class Componentは、Vueのコンポーネントをクラススタイルの構文で作成できるライブラリ
  • @Componentデコレータでクラスにアノテーション(@)を付けることで、Vueを継承したクラスとしてコンポーネントのデータやメソッドを定義することができる
  • 基本的な書き方
    • data:クラスの変数として書く
    • 算出プロパティ:getterとして書く
    • メソッド:普通にメソッドとして書く
    • ライフサイクルフック:名前を合わせてメソッドとして書く

例:シンプルなカウンタコンポーネント

main.vue
<template>
  <div>
    <button v-on:click="decrement">-</button>
    {{ count }}
    <button v-on:click="increment">+</button>
  </div>
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component' // import文でvue-class-componentを呼び出す

// Class定義と同じ構文でVueコンポーネントを定義できる
@Component
export default class Counter extends Vue { // Counterというクラス名でVueコンポーネントを出力する(定義する)
  // Classプロパティは、コンポーネントのデータになる
  count = 0

  // メソッドはコンポーネントメソッドになる
  // 2つのメソッドをCounter Class内で定義
  increment() {
    this.count++
  }

  decrement() {
    this.count--
  }
}
</script>

Vue Property Decoratorが提供するデコレータの他に、@Prop@Watchなどもあるので、簡単に紹介。

代表的なデコレータの紹介

@Prop:親コンポーネントからデータを受け取るpropsを指定

@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
// Propの引数が、プロパティのオプションか、コンストラクター関数か、コンストラクター関数の配列の値がオブジェクトである

Propsの復習

  • 型: Array<string> | Object 例: [ "childComponent" ]
  • propsとは、親コンポーネントからデータを受け取るためにエクスポートされた属性のリスト/ハッシュである

Propsのオプション(PropOptions)は、以下が使える

  • type: String, Number, Boolean, Array, Object, Date, Function, Symbol,カスタムコンストラクタ関数、またはそれらの配列が使える
    • プロパティは、以下のように、(文字列の配列だけでなく)オブジェクトとして列挙できる
// キーと値には、それぞれプロパティ名と型を設定します
props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}
  • default: any - プロパティのデフォルト値を指定 - プロパティが渡されない場合は、この値が代わりに使われる - オブジェクトまたは配列のデフォルト値は、ファクトリ関数で返す必要あり
  • required: Boolean
    • プロパティが必須かどうかを指定
    • 本番環境以外では、この値が真なのにプロパティが渡されないとコンソールに警告が出される
  • validator: Function
    • プロパティの値を唯一の引数として受け取る、カスタムのバリデーション関数
    • 本番環境以外では、この関数が偽を返す (つまり、バリデーションが失敗する) とコンソールに警告が出される

コードの例

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class SampleComponent extends Vue {
  @Prop({ type: String, required: true })
  userName: string;

  @Prop({ type: Boolean, defualt: false })
  isVisible: boolean;
}
</script>

は、以下と同義

<script>
export default {
// プロパティは、オブジェクトとして表現できる
// ①userNameがキーで、{ type: String, required: true }が値となっているオブジェクトと、
// ②isVisibleがキーで、{ type: Boolean, default: false }が値となっているオブジェクトのプロパティを持つクラスである
  props: {
    userName: {
      type: String,
      required: true
    },
    isVisible: {
      type: Boolean,
      default: false
    }
  }
};
</script>

@Emit:子コンポーネントから親コンポーネントにデータを渡す$emitを指定

  • $emitによって装飾された関数が、その戻り値の後に元の引数が続く
  • 戻り値がPromiseの値である場合は、emitされる前に解決される
  • イベントの名前がイベント引数で与えられていない場合は、代わりに関数名が使用されるが、その場合、キャメルケース名はケバブケースに変換されるのに注意
@Emit(event?: string) 

Emitの復習

  • $emit:子コンポーネントから親コンポーネントにデータを渡すために利用する仕組み(カスタムイベント)で、親コンポーネントに対して*なんらかの変化が起こったこと(イベント)を通知(発火)するもの。その際に関連するデータ(オブジェクト、アクション、値など)を添付可能
  • 構文: this.$emit(event [...args])
    • event:イベント名(String型)、[...args]:親コンポーネントに引き渡すデータ
      • 例:this.$emit("任意のイベント名", 渡すデータ); で値を渡し、this.$emit("任意のイベント名")でイベント発火のみ
      • 例2:複数の値を渡すなら、this.$emit( '任意のイベント名', { key1: value1, key2: value2} );のオブジェクトで渡す

コードの例

import { Vue, Component, Emit } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  count = 0

  @Emit()
  addToCount(n: number) { // キャメルケースからケバブケースに変わります
    this.count += n
  }

  @Emit('reset') // イベント発火のみ
  resetCount() {
    this.count = 0
  }

  @Emit()
  returnValue() { // 値そのものを返す場合
    return 10
  }

  @Emit()
  onInputChange(e) {
    return e.target.value
  }

  @Emit()
  promise() { //Promiseオブジェクトを戻り値として返す場合、emitされる前に解決されます
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(20)
      }, 0)
    })
  }
}

は、以下と同義

export default {
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    addToCount(n) {
      this.count += n
      this.$emit('add-to-count', n)
    },
    resetCount() {
      this.count = 0
      this.$emit('reset')
    },
    returnValue() {
      this.$emit('return-value', 10)
    },
    onInputChange(e) {
      this.$emit('on-input-change', e.target.value, e)
    },
    promise() {
      const promise = new Promise((resolve) => {
        setTimeout(() => {
          resolve(20)
        }, 0)
      })

      promise.then((value) => {
        this.$emit('promise', value)
      })
    },
  },
}

@Watchプロパティを使う

  • dataの変更を検知するためのメソッドを定義します。
  • optionにwatchオプションのimmediateやdeepもオブジェクトとして渡すことで指定できます。
@Watch(path: string, options: WatchOptions = {})

watchオプションの復習

  • 型: { [key: string]: string | Function | Object | Array}
  • WatchOptionsは、キーが評価する式、値は、キーに対応するコールバックをもつオブジェクト
  • 値はメソッド名の文字列、または追加のオプションが含まれているオブジェクトが取れる
  • Vue インスタンスはインスタンス化の際にオブジェクトの各エントリに対して$watch()を呼ぶ

コード例

import { Vue, Component, Watch } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Watch('child')
  onChildChanged(val: string, oldVal: string) {}

  @Watch('person', { immediate: true, deep: true })
  onPersonChanged1(val: Person, oldVal: Person) {}

  @Watch('person')
  onPersonChanged2(val: Person, oldVal: Person) {}
}

は、以下と同義

export default {
  watch: {
    child: [
      {
        handler: 'onChildChanged',
        immediate: false,
        deep: false,
      },
    ],
    person: [
      {
        handler: 'onPersonChanged1',
        immediate: true,
        deep: true,
      },
      {
        handler: 'onPersonChanged2',
        immediate: false,
        deep: false,
      },
    ],
  },
  methods: {
    onChildChanged(val, oldVal) {},
    onPersonChanged1(val, oldVal) {},
    onPersonChanged2(val, oldVal) {},
  },
}

参考

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

vuex-module-decorators(Vuex、store)のjestを書く

初めに

自分のメモ用として記載しています。
端折って書いている場所もある為、ご了承ください。

vuex-module-decoratorsを使ってstoreの作成をしていた際に
テスト(jest)をどう書けばわからなかった経緯があった為記載します。

*以下の記事などでも書いて下さっているのですが、自分の場合は正常に動作しなかった為
https://qiita.com/azukiazusa/items/8a158913c870bc0c8ba9

storeの実装について

src配下に以下のように作成したとします。

store
├── index.ts
├── app.ts
└── utils
    ├── accessor.ts
index.ts
import { Store } from 'vuex';
import { initializeStores } from '@/store/utils/accessor';

const initializer = (store: Store<any>) => initializeStores(store);

export const plugins = [initializer];
export * from '@/store/utils/accessor';

accessor.ts
import { Store } from 'vuex';
import { getModule } from 'vuex-module-decorators';
import AppModule from '@/store/app';

let appModule: AppModule;

function initializeStores(store: Store<any>): void {
  appModule = getModule(AppModule, store);
}

export {
  initializeStores,
  appModule
};

*型などは適当に埋めています。こんなstoreを作成していると見てください

app.ts
import { Module, Action, VuexModule, Mutation } from 'vuex-module-decorators';
import { $axios } from '@/store/utils/api.ts';

@Module({
  name: 'app',
  stateFactory: true,
  namespaced: true
})
export default class AppModule extends VuexModule {
  private app: any = [];

  @Mutation
  set(app: any) {
    this.app = app;
  }

  @Action({ rawError: true })
  async fetchApp(type?: string): Promise<any> {
    const params = {
      
    };

    await $axios
      .$get('', { params })
      .then((res) => {
        this.set~(res.item);
      });
  }

  get appList(): any {
    if (!this.app) {
      return [];
    }

    return this.app;
  }
}

app.tsのテストを書く

yarn buildを行うと.nuxtが作成されます。
そのstore.jsにあるcreateStoreを使ってstoreのAppModuleを利用できるようにしました。

app.spec.ts
import { getModule } from 'vuex-module-decorators';
import AppModule from '@/store/app';
import { createStore } from '@/../.nuxt/store';

jest.mock('@/store/utils/api.ts', () => ({
  $axios: {
    $get: jest.fn(() =>
      Promise.resolve({
        item: [{ name: '' }],
        total: 10
      })
    )
  }
})); // $axiosのmockです。

let AppModule: any;

beforeAll(() => {
  const store = createStore();
  AppModule = getModule(AppModule, store); // こちらです。
});

describe('AppModule', () => {
  test('appList', async () => {
    await AppModule.fetch();
    expect(AppModule.appList).toStrictEqual([
      { name: '' }
    ]);
  });
});

最後に

かなり無理やりなテストの書き方だと思うのですが、他に良い方法があれば教えて頂きたいです。

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

laraval8 + vue.js2.6で、親子(parent-child)component間の通信について

この本を書いたきっかけは?何についての記事?

「これから始める、Vue.js実践入門」(山田祥寛/著、SBクリエイティブ株式会社/発行)のリスト4-14あたりをlaravel8のプロジェクト内でやりたかったので、コードを少し改変してみた。

参考にさせていただいたサイトなど

https://orkhan.dev/2020/04/14/accessing-parent-and-child-component-in-vuejs/
https://laracasts.com/discuss/channels/vue/vuejs-component-within-component-getting-data-from-child-component
https://vegibit.com/how-to-create-a-child-component-in-vuejs/
https://stackoverflow.com/questions/45387307/define-vue-component-in-laravel

前置きはこれくらいにして

app.js
import App3 from './components/MyComponent3.vue'
const app3 = new Vue({
    el: '#app3',
    components: {
         App3
    },
});
MyComponent3.vue
<template>
  <div>
    <div>
      <p>現在値{{ current }}</p>
    </div>
    <mycomponent4 step="1" v-on:plus="onplus"></mycomponent4>
    <mycomponent4 step="2" v-on:plus="onplus"></mycomponent4>
    <mycomponent4 step="-1" v-on:plus="onplus"></mycomponent4>
  </div>
</template>

<script>
import mycomponent4 from './MyComponent4.vue';
export default {
components: {mycomponent4},
 data: function () {
    return {
        current: 0,
    }
 },
  methods: {
    onplus: function (e) {
      this.current += e;
    },
  },
};
</script>
MyComponent3.vue
<template>
<button type="button" v-on:click="onclick">
  {{ step }}
  </button>
</template>
<script>
export default {
 data: function () {
    return {
        current: 0,
    }
 },

    props: ['step'],
    methods: {
        onclick:function() {
            this.$emit('plus', Number(this.step));
        }
    }
};
</script>
index.blade.php
<!doctype html>
<html lang="ja">
<head>
    <title>Index</title>
    <link href="{{ mix('css/app.css') }}"  rel="stylesheet" type="text/css">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" />
</head>
<body>
    <div id="app3">
        <app3>
        </app3>
    </div>
    <script src="{{ mix('js/app.js')}}">
    </script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
</body>
</html>
HelloController.php
class HelloController extends Controller
{
    public function index()
    {
        return view('hello.index', $data);
    }
}

qiita用.png

※ボタンを押すと、現在値の数字が、ボタンに書かれている数だけ変動します。

ここまで読んでいただき、ありがとうございました。何かの参考になれば幸いです。

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

FirebaseとVue.jsでSPAのOGPを作ったらSEOが壊滅したので回避した

はじめまして。
マインドツリーを使って自問自答することで考えを深めたり、周囲の人に質問してもらうことで答えのない問いを問いていくサービスQ&Qを作っているあどにゃーです。

ショートサマリ

今回は、Vue.js+CloudFunctionsでOGP対応したら
・TwitterやSlackには任意の動的画像を表示することができるようになった(OK)
・Googleクローラは真っ白で何も読み込まなくなった(NG)
ので対策したよという内容です。
スクリーンショット 2021-01-11 1.22.03.png

前知識

SPA(Single Page Application)のVue.jsでは、各ページのOGP(Open Graph Protocol)を動的に返せない問題があります。理由は、botがjavascriptを解釈しないからです。でも、TwitterやSlackにリンクを貼る時に全部同じOGP画像というのはイケてない。。
この解決策としては、
・SSR(サーバサイドレンダリング) → 開発量が大きく腰が思い:frowning2:
・Pre-rendering → 動的ページがたくさんある場合向いてない:thinking:
・CloudFunctions → 一番手軽にOGP生成できる:relaxed:
があると思います。簡単なCloudFunctionsで解決したい!
そこで、ゆきさんの『SNS映えするWebアプリを...!FirebaseとVue.jsでSPAのOGP画像の動的生成をやってみたら案外楽だった』を参考に、CloudFunction+Vue.jsでOGPを生成しました。丁寧な神解説で非常に助かりました。
簡単に処理の流れを書くとこんな感じです。
1. 動的にOGPを生成したいPathでcloud functionsを発動
2. BotはJavaScriptを理解できずfunctionsで生成したhtmlを読み取って終了
3. UserはJavaScriptを理解するのでリダイレクトされた本来のhtmlに飛んで通常のコンポーネントがマウントされて終了

Google検索が壊滅...

OGP対策してめでたしめでたし:relaxed:だったのですが、Googleからの検索が一切なくなっている。。おかしいなと思って、varidationサイトで検証してみるとGoogleはなんと真っ白。。。松崎しげるの歯より白い。一方で、Twitterの検索ではOGPも生成されており、metaデータも正しく読み込めています。一体何が起きているのでしょうか。
スクリーンショット 2021-01-11 1.22.03.png
元TreeのURL
https://qnqtree.com/tree/iLNP7RAY2KpLNlGFiSQE
Googleの検証用URL
https://search.google.com/test/mobile-friendly
Twitterの検証用URL
https://cards-dev.twitter.com/validator

何が起こっている?

Vue.jsでCloud Functionsを使ってOGPを動的に生成したのは、BotがJS(javascript)を解釈できないからでした。
一方でGoogleの検索クローラはJSを解釈します。解釈するため、リダイレクト先に飛び、ロードされたVue本来のindex.htmlを読み取りに来ます。
しかし、Vue側でFirestoreからデータを取得してコンポーネントを更新している場合、検索クローラはその処理を待ってくれません。結果的に、クローラはFirestoreからデータを取得する前の真っ白な画面を取得して帰っていきます:angel_tone2:。。。

スクリーンショット 2021-01-11 15.04.25.png
流れを書くと下記になります。
1. 動的にOGPを生成したいPathでcloud functionsを発動
2. JSを理解できないBOTはfunctionsで生成したhtmlを読み取って終了(Twitter Bot)
3. JSを理解できるBOTはリダイレクト先まで飛ぶが、firestoreからのデータ取得を待たずに終了(Google Bot)
4. JSを理解できるUserは、firestoreからデータ取得後の更新されたhtmlを取得して終了(Browser)

回避策

JSを中途半端に解釈するBOTと解釈しないBOTの存在が混乱の元なので、BOTの登録をして、BOTの場合は明示的にリダイレクトしないように変更します。存在するBOT全部を登録できるわけではないので漏れが発生しますが、暫定的な対策としてはワークすると思います。
スクリーンショット 2021-01-11 16.24.25.png
コードはこんな感じで、Botを明示的に記述しています。

exports.ogp = functions.https.onRequest(async (req, res) => {
  // botの判定
  const userAgent = req.headers['user-agent'].toLowerCase()
  const isBot = userAgent.includes('googlebot') ||
    userAgent.includes('yahoou') ||
    userAgent.includes('bingbot') ||
    userAgent.includes('baiduspider') ||
    userAgent.includes('yandex') ||
    userAgent.includes('yeti') ||
    userAgent.includes('yodaobot') ||
    userAgent.includes('gigabot') ||
    userAgent.includes('ia_archiver') ||
    userAgent.includes('facebookexternalhit') ||
    userAgent.includes('twitterbot') ||
    userAgent.includes('developers.google.com') ? true : false
   // Botならリダイレクトしない, Botじゃなければリダイレクト
   if (isBot) {
     res.status(200).send(
    `<!doctype html>
        <head>
          // 更新するmeta dataを記述
        </head>
        <body>
          // リダイレクトしない
          <header>${TITLE}</header>
          <main>${DESCRIPTION}</main>
        </body>
      </html>`
     )
   } else {
      `<!doctype html>
        <head>
          // 更新するmeta dataを記述
        </head>
        <body>
          <script>
            // クローラーにはメタタグを解釈させて、人間は任意のページに飛ばす
            location.href = '${SITEURL}/_tree/${treeId}'
          </script>
        </body>
      </html>`
   }

注意

動的pathの先のdataにユーザ権限がある場合は注意が必要です。例えば、筆者のサービスの場合、tree/{treeId}は各ユーザの設定で非公開と公開のデータがあります。 cloud functionsは管理者権限で実行するため、非公開のデータもOGPやmeta用のデータを取得しようとします。
このように権限がある場合は、dataが非公開か、公開かを判定して、cloud functionsでhtmlを更新するかどうかの判断ロジックも追加が必要です。

まとめ

BOTを判定してリダイレクトしないようにしたので、Googleのクローラも内容を読み込むことができるようになりました:relaxed:。下図を参照ください。これで検索クローラも各Treeの内容が読み込めるようになったので、SEO的にもまったく検索に引っかからないということはなくなったと思います。
スクリーンショット 2021-01-11 16.58.15.png
しばらくこれでQnQにGoogle検索から流れてくる人がいるか様子を見てみます。

ちょろっとQnQの紹介

ちなみにQnQはふと感じた疑問や感情を深堀りするサービスです。なぜこの映画を面白いとおもうのか、なぜこの料理が嫌いなのか、といったことを考えることで自分の価値観に気づいて脱マニュアル人間化することを目的にしています。興味があったら使ってみてください:relaxed:
https://qnqtree.com/about

おわりに

最終的にはSSR(Server Side Rendering)をするのが王道かと思いますが開発がヘビーなので暫定でcloud functionsで対策をしました。
もっと良い解決策がありましたらコメントいただけると幸いです。
ご拝読ありがとうございました。

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

Vue.js のグローバルコンポーネントを使ってみた

初めに

前回に引き続きVue.jsで学んだことのアウトプットとして投稿します。

コンポーネントの中にメソッドを書く

  <div id="app">
    <new-btn></new-btn>
    {{ message }}
  </div>
Vue.component('new-btn', {
  template: `
  <button v-on:click='displayDate'>
    日付を表示</button>
  `,
  methods: {
    displayDate: function() {
      date = new Date();
      app.message =
        date.getFullYear() + '/' +
        ('0' + date.getMonth() + 1).slice(-2) + '/' +
        ('0' + date.getDate()).slice(-2) + '-' +
        ('0' + date.getHours()).slice(-2) + ':' +
        ('0' + date.getMinutes()).slice(-2) + ':' +
        ('0' + date.getSeconds()).slice(-2);
    }
  }
})

var app = new Vue({
  el: '#app',
  data: {
    message: ''
  }
})

image.png

これは次のコードと同じ結果になる。

  <div id="app">
    <button v-on:click="displayDate">日付を表示</button>
    {{ message }}
  </div>
var app = new Vue({
  el: '#app',
  data: {
    message: ''
  },
  methods: {
    displayDate: function() {
      date = new Date();
      this.message = 
        date.getFullYear() + '/' +
        ('0' + date.getMonth() + 1).slice(-2) + '/' +
        ('0' + date.getDate()).slice(-2) + '-' +
        ('0' + date.getHours()).slice(-2) + ':' +
        ('0' + date.getMinutes()).slice(-2) + ':' +
        ('0' + date.getSeconds()).slice(-2);
    }
  }
})

コンポーネントの中にデータオブジェクトを書く

コンポーネントの中に data を書く。メソッドが呼ばれるたび、data の中の count にアクセスして +1 する。

Vue.component('new-btn', {
  template: `
  <button v-on:click='displayDate'>
  日付を表示{{ count }}回</button>
  `,
  methods: {
    displayDate: function() {
      date = new Date();
      app.message =
        date.getFullYear() + '/' +
        ('0' + date.getMonth() + 1).slice(-2) + '/' +
        ('0' + date.getDate()).slice(-2) + '-' +
        ('0' + date.getHours()).slice(-2) + ':' +
        ('0' + date.getMinutes()).slice(-2) + ':' +
        ('0' + date.getSeconds()).slice(-2);
      this.count++;
    }
  },
  data: function() {
    return {
      count: 0
    }
  }
})

var app = new Vue({
  el: '#app',
  data: {
    message: ''
  }
})

image.png

コンポーネントのプロパティで日付を何回も出現させる

  <div id="app">
    <button v-on:click="displayDate">ボタン</button>
    <date-display
      v-for="dateText in dateList"
      v-bind:date="dateText"
    ></date-display>
  </div>
Vue.component('date-display', {
  props: ['date'],
  template: '<p>{{ date }}</p>'
})

var app = new Vue({
  el: '#app',
  data: {
    dateList: []
  },
  methods: {
    displayDate: function() {
      dateNow = new Date();
      var dateText =
        dateNow.getFullYear() + '/' +
        ('0' + dateNow.getMonth() + 1).slice(-2) + '/' +
        ('0' + dateNow.getDate()).slice(-2) + '-' +
        ('0' + dateNow.getHours()).slice(-2) + ':' +
        ('0' + dateNow.getMinutes()).slice(-2) + ':' +
        ('0' + dateNow.getSeconds()).slice(-2);
      this.dateList.push(dateText)
    }
  }
})
  • 動作結果

image.png

参考記事

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

効率的にサイト作り!Buefyでアイコンを表示しよう!!

僕は今年二十歳の代なので、今日は成人式に行ってきます!

なので、今日は時間がないため少し短めの内容となっております。

タイトルの通り、Buefyでアイコンを表示していきます。

アイコンの表示は他にもVuefyで行うこともできます。そのやり方は僕が書いた記事え、アイコンでまだFontAwesome使ってるの???を参考にして頂くと分かりやすいかと。

また、Buefyなんて聞いたの初めてという方は、こちらの記事初心者必見!サイト制作は楽してなんぼ。CSSフレームワークBuefyの紹介!!を見ていただくと分かると思います!

それでは、Buefyを使ってアイコンを表示してみましょう!

使いたいアイコンを決める

Fontawesomeの公式サイトから使いたいアイコンを検索して決めてください。

今回はTwitterアイコンを例にします。

b-iconタグで設定

使いたいアイコンを決めたら、<i class="fab fa-twitter"></i>このような欄があると思います。一番最初に、「fab」の部分をpackで指定します。

App.vue
<b-icon pack="fab"></b-icon>

次に、「fa-twitter」の部分をiconで設定し、適当にサイズを決めます

App.vue
<b-icon pack="fab" icon="twitter" size="medium"></b-icon>

テキストとの高さを揃える

<b-icon>は時によっては、テキストと高さが合わない場合があります。なので、以下を参考にしてstyleを設定してください。

App.vue
       <b-icon
          pack="fas"
          icon="user-plus"
          size="medium"
          style="margin-right: 0.5rem; margin-bottom: 0.25rem; vertical-align: middle"
        ></b-icon>

めちゃ簡単♪

以上、「効率的にサイト作り!Buefyでアイコンを表示しよう!!」でした!

良ければ、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

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

Vue.jsを学習した振り返り

はじめに

Vue.jsを学習した内容についての投稿

目次

・Vue.jsのメリット
・Vue.jsの基本的な使い方
・Vue.jsインスタンスを作る
・バインディングとは
・テイストとデータの(紐付け)バインデジング
・属性とデータの(紐付け)バインディング
・ディレクティブの省略記法
メソッドについて

Veu.jsのメリット

・Vue.jsはDOM操作を自動で行うため、データやイベントが多くなってもコードが複雑になりにくい。

Veu.jsの読み込み

Vue.js
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Vue.js Test</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="example">{{ greeting }}</div>
    <script src="main.js"></script>
  </body>
</html>
Vue.js
const app = new Vue({
  el: '#example',
  data: {
    greeting: 'Hello Vue.js!',
  },
});

プレビューすると'Hello Vue.js!'と表示される。

Vue.jsを読み込みはhead要素内にいれる

Vue.js
<head>
    <meta charset="utf-8">
    <title>Vue.js Test</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

head内要素にいれる理由は、ページ表示した最初の一瞬だけHTMLファイル書いた部分が
見えてしまう場合があり、これはHTMLファイルに記述した{.....}をVue.jsが処理するまでに
少し時間がかかり、head要素内に入れる方がbodyの直前に入れるより処理が早く表示を抑えることができる。

Vueのインスタンス作成

newは演算子の一つでVue ( {  } )はオブジェクトを複製するための特別な関数。特別な関数のことを
new コンストラクタという。複製したオブジェクトのことをVue インスタンスといいます。

const app = new Vue( )
①Vue   コンストラクタ
②new Vue コンストラクタの呼び出し
③app    インスタンスの格納  

Vueコンストラクターの引数には、オブジェクトを渡す。Vue.jsでオブジェクト渡すことをオプションオブジェクトという。オプションオブジェクトでプロパティを設定することでVue.jsの機能を使うことができます。

Vue.js
const app = new Vue({
  el: '#example',
  data: {
    greeting: 'Hello Vue.js!',
  },
});

elとdataのプロパティをオプションオブジェクトに設定しています。

elオプション
elオプションのelは「element(要素)」の略称。elオプションにはDOM要素を設定します。
上記のmain.jsのコードでは#exampleを設定しています。index.html内のid属性div id="example"を
設定したことになります。設定した要素内のHTMLは、テンプレートというものになります。
テンプレート内ではVue.jsの機能が使えます。また、テンプレートはVue.jsを通してブラウザに表示されます。

Vue.js
<div id="example">{{ greeting }}</div>

Vueの管理下に置かれたことになります。

dataオプション
dataオプションはデータを設定する。Vueインスタンスのプロパティにアクセスできます。

Vue.js
data: {
    greeting: 'Hello Vue.js!',
  },

データオプションのオブジェクトにはgreetingという名前を設定して、その値に'Hello! vue.js'という文字列を設定。

index.htmlをプラウザで開きデベロッパーで確認すれば

Vue.js
>app.grreeting
"Hello! Vue.js"

と表示されます。

テキストとデータを結びつける

Vueインスタンスのデータを表示させるにはHTML内で{{...}}を使用する。{{...}}の中にはデータオプションで指定したオブジェクトのプロパティを書き込む。

Vue.js
<div id="example">{{ greeting }}</div>
Vue.js
const app = new Vue({
  el: '#example',
  data: {
    greeting: 'Hello Vue.js!',
  },
});

この時dataやappの名前は不要。

{{...}}をmustache(マスタック)構文という。
HTMLでテンプレート{{...}}を使用して、Vueインスタンスのdataオプション内のデータと結びつけることを、
データバインディング(結びつける)という。

マスタック構文を使ってVueインスタンスのdataオプションとバインディング(結びつき)させる。
バインディングする事でデータが自動で変更され、Vue.jsによってDOM操作が自動で処理される。

属性とデータを結びつける

属性とVueインスタンスのデータをバインディングすることもできる。属性の場合はマスタック構文は使えない。
その場合はv-bind属性を使用する。v-bindなどの特別な属性をディレクティブという。

Vue.js
<div id="example">
  <a v-bind:href="url">Google</a>
</div>
Vue.js
const app = new Vue({
  el: '#example',
  data: {
    url: 'https://www.google.com/',
  },
});

上記の場合はa要素のhref属性値をVueインスタンスのdataオプションurlプロパティと
バインディング(結びつけ)しています。v-bind:のあとに記述されているのがv-bindの引数になります。

v-for 配列する処理

v-forは、配列やオブジェクトを繰り返し処理できます。配列は格納した変数  in  配列の構文を使用する。

書き方

Vue.js
<ul id="#example">
   <li
    v-for="member in members "
    v-bind:key="members"
>
   {{ member }}
   </li>
</ul>
Vue.js
const app = new Vue({
  el: '#example',
  data: {
    members: ['太郎', '山田', '佐藤', '鈴木'],
  },
});

上記の場合はmembersが配列。Vueインスタンスのデータオプション内で設定した配列になります。
v-bind:keyはVueが要素は別々であるとして識別するのに必要になる。

その他の書き方

Vue.js
<ul id="#example">
   <li
    v-for="(member,index) in members "
    v-bind:key="index"
>
    {{ index }} : {{ member }}
   </li>
</ul>
・0 : 太郎
・1 : 山田
・2 : 佐藤
・3 : 鈴木

リスト表示にインデックス(番号)を付け加えることができる。

v-for オブジェクトの繰り返し処理

プロパティの値を格納した変数  in  オブジェクトの構文を使用する。

Vue.js
<ul id="#example">
   <li
    v-for="character" in characters "
    v-bind:key="character"
>
    {{ character }}
   </li>
</ul>
Vue.js
const app = new Vue({
  el: '#example',
  data: {
    characters: {
      symbol: ミッキー,
      girlfriend: ミニー,
      duck: ドナルド,
  },
});

結果

・ミッキー
・ミニー
・ドナルド

オブジェクトの繰り返し処理2

(プロパティの値を格納した変数,  プロパティ名を格納した変数)  in  オブジェクトでプロパティ名を利用できる。

オブジェクトの繰り返し処理2

(プロパティの値を格納した変数,  プロパティ名を格納した変数)  in  オブジェクトでプロパティ名を利用できる。

Vue.js
<ul id="#example">
   <li
    v-for="(character, key)" in characters "
    v-bind:key="key"
>
   {{ character }} : {{ character }}
   </li>
</ul>
Vue.js
const app = new Vue({
  el: '#example',
  data: {
    characters: {
      symbol: ミッキー,
      girlfriend: ミニー,
      duck: ドナルド,
  },
});

結果

Vue.js
symbol: ミッキー
girlfriend: ミニー
duck: ドナルド

v-on イベント処理

v-onは要素のイベントを処理する。

Vue.js
<div id="example">
     <input
    v-bind:value="name"
    v-on:input="name = $event.target.value"
     >
     <p>{ { name } }</p>
</div>
Vue.js
const app = new Vue({
  el: '#example',
  data: {
    name: '太郎',
  },
});

この場合、input要素に入力することで、$event.target.valueの式が実行される。
($event)の$はjQueryで使用したものと関係ない。

ディレクティブの省略記法

v-bind、v-onは省略して書くことができる。

下記のコードを

Vue.js
<input  v-on:input="name = $event.target.value" :value="name">

v-on:を@マーク、v-bind:を : に置き換えれる

Vue.js
<input  @:input="name = $event.target.value" :value="name">

※コードを省略する時は必ず統一する。

v-model フォーム入力

複数のフォーム入力はv-modelを使う。(v-bind、v-onを複数の要素に結びつけるとコードが複雑になる)

Vue.js
<div id="example">
    <input v-model="name">
    <p>{{ name }}</p>
</div>
Vue.js
const app = new Vue({
  el: '#example',
  v-model: {
    name: '太郎',
  },
});

※下記の場合はname

Vue.js
<input v-model="Vueインスタンスデータのdataオプションに設定したプロパティ名">

input要素のイベントが発生したら、指定したデータの値を更新する処理が行われ、
v-bind、v-onを使わなくても双方向のバインディング(結びつけ)ができます。

算出プロパティ

計算処理(難しい処理)を行うときは算出プロパティを使う。
※難しい計算処理をテンプレート(HTML)内に記述するとコードが複雑でエラーの原因になります。
   算出プロパティでテンプレートも見やすくなり、関数を使うことで複数の式を使用することも
   できる。

メソッド

メソッドもオプションオブジェクトのプロパティとして書く。オプション名はmethods。

算出プロパティ(computed)とメソッド(methods)の使い分け

算出プロパティはキャッシュ(一時保存)され、データが更新された時だけ再計算される。

index.html

Vue.js
<div id="example">
      <p>
        <button v-on:click="countUp()">{{ count }} </button>
      </p>
      <p>現在時刻(メソッド):{{ getDate() }}</p>
      <p>現在時刻(算出プロパティ): {{ date }}</p>
</div>
Vue.js
const app = new Vue({
  el: '#example',
  data: {
   count 0,
  },
  computed: {
   date() {
      return newDate().tolocalStrings();
    },
  },
  methods() {
    countUp() {
     this.count += 1;
    },
    getDate() {
     return newDate().toLocalStrings();
     },
   },
});

ボタンクリックでcountUp(   )メソッドが呼び出され、Vueインスタンスのデータのcountの値、
countと結びついた要素内のテキスト(数字)も変化し、現在時刻(メソッド)も更新される。
メソッド(methods)は再描画されるたびに更新されるが、算出プロパティはVueインスタンス
データの値が変更になった場合のみ再計算される。

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

単一ファイルコンポーネントの原則をrails6のアプリに組み込んでみた。いまいちよくわからん。

単一ファイルコンポーネントの原則

コンポーネントごとにvueファイルを作成する開発の進め方のこと。つまりパーツ単位でHTMLをまとめてコンポーネントとして扱いどこでも呼び出せるようにすること、を指すと考えて良さそう。

これの反対をグローバルコンポーネント、といい

new Vue({ el: '#container '})

といった形でcontainerをターゲットにしたvueファイルを作り、各ページで読み込む。この書き方は小規模開発では優れているが、大規模な開発になるといくつか不具合が生じる。中でも全てのコンポーネントの変数定義が共通になるのはちょっと避けたい。よって使用するHTMLごとにコンポーネント化する単一ファイルコンポーネントの原則に従って開発するのがベター。

単一コンポーネントでvueファイルを書くとこんな感じ

<template>
  <div class="side_bar">SideBar</div>
</template>

<script>
</script>

<style>
 .side_bar {
  width: 300px;
  background-color: green;
}
</style>

templateの部分にHTML、scriptでjs、styleでcssを指定する。これをHTML部品ごとに作る。こうすると部品ごとに一つのファイルで扱えるのでやり直しがしやすい。またこのクラスにあたってるcssどれ?みたいな疑問もなくなる。

作ったコンポーネントをrailsアプリで読み込むわけだが今回は

各コンポーネントを読み込む親のjsがエントリーファイルとなっている。こいつをbuildしてrailsアプリで読み込むと各コンポーネントの設定がアプリに反映される仕組み。

main.js(エントリーファイル)

import Vue from 'vue';
import App from './App.vue';

// App.vueをエントリとしてレンダリング
new Vue({
  el: '#app',
  render: h => h(App)
})

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

<template>
  <div class="container">
    <sidebar></sidebar>
    <chat-container></chat-container>
  </div>
</template>

<script>
import Sidebar from './components/Sidebar.vue'
import ChatContainer from './components/ChatContainer.vue'

  export default {
    components:{
      Sidebar,
      ChatContainer
    }
  }
</script>

<style>
.container {
  display: flex;
  margin: auto;
  width: 70%;
  height: 100vh;
}
</style>

こいつをwebpackでコンパイルする。webpack.config.jsは以下の通り

module: {
    rules: [
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        loader: 'vue-loader'
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: ['style-loader', 'css-loader'],
      },
    ]
  },
    (省略)
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
    }
  },
  plugins: [new VueLoaderPlugin()]

重要なのはloaderとresolveオプションかなと思うので関係ないところ
は省略。ちょっとハマったのはcss-loaderとstyle-loaderを読み込む際にはなぜかloaderオプションじゃなくてuseオプションにしないといけなかったこと。複数loaderを読み込むからなのかな?

コンパイル後にrailsサーバーを立ち上げると画像のように別々のファイルに書いたhtmlとcssがきっちり反映されて一つのビューにまとまっていることがわかる。

ChatVueApp_と_20200111_md.png

参考

・単一ファイルコンポーネントについての公式ドキュメント

https://jp.vuejs.org/v2/guide/single-file-components.html

・単一ファイルコンポーネントをwebpackを使って実現する方法について

https://qiita.com/tkhr/items/ac22019c891fe8fa5f91

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

Vue2で[vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function.

表題のエラーに出会いました。

codesandboxにて、Composition APIで遊んでいた時に表題のエラーに出会ったので備忘録。
Vueは2系を使用しています。

ソース

[vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function.
該当のエラー箇所はimport下のref
でもなぜこれでエラーになるのか・・・

// Hello.vue
<template>
  <div class="hello">
    <p class="display-nuber">{{ num }}</p>
    <button @click="increment">increment</button>
  </div>
</template>

<script lang='ts'>
import { defineComponent, ref } from "@vue/composition-api";
const num = ref(1); // <- ここでエラー

export default defineComponent({
  name: "test",
  setup() {
    const increment = () => {
      num.value++;
    };

    return {
      num,
      increment,
    };
  },
});
</script>
// main.js
import Vue from "vue";
import App from "./App.vue";
import VueCompositionApi from "@vue/composition-api";

Vue.use(VueCompositionApi);
Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App)
}).$mount("#app");

とりあえず解決

調べていくうちにこちらのissueを見つけました。
https://www.notion.so/Composition-API-24c25585c44e4559802d7bbf497511b3#eb149d74d549447bbc08cdfd63691926

内容を見ていくと、export defaultの中でrefを使えば解決しそうな雰囲気。
ということで中に入れました。

// Hello.vue
<template>
  <div class="hello">
    <p class="display-nuber">{{ num }}</p>
    <button @click="increment">increment</button>
  </div>
</template>

<script lang='ts'>
import { defineComponent, ref } from "@vue/composition-api";

export default defineComponent({
  name: "test",
  setup() {
    const num = ref(1); // <- export defaultの中に入れた
    const increment = () => {
      num.value++;
    };

    return {
      num,
      increment,
    };
  },
});
</script>

解決。これでエラーは出なくなりました。

少し深堀り

こちらのstack overflowを見てみると

In short you need to create a separate file that will install the composition API plugin and call that file within the router/index.ts file to instantiate the plugin.
It’s because of the composition API not being inside Vue itself.

Vue2系が内部にComposition APIを持っていないことが原因のよう。
また読み進めていくとプラグインを読み込むファイルを外に切り出せばうまくいきそうです。

// installCompositionApi.js
// 新たにこのファイルを作成

import Vue from "vue";
import VueCompositionApi from "@vue/composition-api";

Vue.use(VueCompositionApi);
// main.js
import "./InstallCompositionApi"; // 追加
import Vue from "vue";
import App from "./App.vue";
// import VueCompositionApi from "@vue/composition-api"; 削除

// Vue.use(VueCompositionApi); 削除
Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App)
}).$mount("#app");

// Hello.vue
<template>
  <div class="hello">
    <p class="display-nuber">{{ num }}</p>
    <button @click="increment">increment</button>
  </div>
</template>

<script lang='ts'>
import { defineComponent, ref } from "@vue/composition-api";
const num = ref(1); //元の位置に戻した(export defaultの外)

export default defineComponent({
  name: "test",
  setup() {
    // const num = ref(1); 削除
    const increment = () => {
      num.value++;
    };

    return {
      num,
      increment,
    };
  },
});
</script>

これでexport defaultの外でrefを使用してもエラーは出なくなりました。

Vue3では?

Composition APIがプラグインではなく正式に追加されているため、こちらのエラーは解消されているようです。
実際にcodesandboxで動かしてみましたが、これまでに記述したことをしなくてもrefexport defaultの外に出すことができました。
おわり。

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