20200807のvue.jsに関する記事は15件です。

Nuxt.js × microCMS × Netlify でJamstackな構成のブログを作ってみた。

はじめに

ヘッドレスCMSを用いたブログサイトをJamstackな構成で作ってみたので、
それについてまとめておこうと思います。

作ったサイトはこちらになります。
https://www.d-suke-notebook-blog.com/

実行環境

macOS 10.15.5
node.js 12.18.2
Yarn 1.22.4
Nuxt.js 2.14.0

Jamstackとは

実際にブログ作成の過程をまとめる前にJamStackについて簡単に触れておこうと思います。
JamStackは一言で言うとwebサーバーに依存しない構成のことですね。

具体的には、事前にレンダリングしたコンテンツを配信して、動的な処理を行いたい場合はAPIで行います。
詳しい説明はこちらがおすすめです。
https://qiita.com/ozaki25/items/4075d03278d1fb51cc37

これによって、ユーザーがアクセスした時にはただのHTMLでしかないので、
従来のwebアプリや、wordpressのようなCMSよりも応答速度が速くなります。
また、セキュリティの面においても攻撃対象がほぼないため、自然と高くなります。

雛形の作成

まずはプロジェクトの雛形を作るため、create-nuxt-appを使います。
次の手順でインストール、使用します。

$ npm i -g create-nuxt-app
$ create-nuxt-app プロジェクト名

いろいろ聞かれるので状況に合わせて必要なものを答えていきます。
ここではmodulesにaxiosとdotenvを指定します。

これでプロジェクトの雛形テンプレートができました。

コンテンツの取得、表示

microCMSからデータを取得する処理について書いていきます。

データの保存場所

今回はvuexにデータを保存します。
その際、nuxtServerInit() で最初のサーバーとの通信の際に一回だけ取得し、
stateに保存します。
これは、ビルド時には一回取得すればいいため最初のアクセスだけ呼び出される
nuxtServerInit を使います。

microCMSのブログの方ではasyncDataを使っていますが
その場合は取得済みのトークンなどを設定して過剰に通信しないようにすると
ビルド時間も短縮できると思います(無料枠には時間の制限があるため)。

vuexの記述

storeディレクトリのindex.jsにモジュールモードで記述していきます。

export const actions = {
  async nuxtServerInit({ commit }, { $config }) {
    const resArticles = await this.$axios.$get(`${$config.apiUrl}blog`, {
      headers: { "X-API-KEY": $config.apiKey }
    });
    const resTags = await this.$axios.$get(`${$config.apiUrl}tag`, {
      headers: { "X-API-KEY": $config.apiKey }
    });
    commit("setArticles", resArticles.contents);
    commit("setTags", resTags.contents);
  }
};

非同期での通信になるのでactionsで行います。
データの流れとしてはactionsからmutationsを通してstateに保存、となっています。

ここで注意する点として、API-keyの隠し方です。
こちらにあるように

https://microcms.io/blog/nuxt-secure-api-key

単純にprocess.env.API_KEY で取得すると、
ビルドされたファイルに定数に変換され残ってしまいます。

なので、JamStack構成にする際の隠し方としては、nuxtのv2.13から追加された
privateRuntimeConfigを使います。

nuxt.config.jsに

require("dotenv").config();
const { API_KEY } = process.env;
const { API_URL } = process.env;

export default {
 ~~~
 privateRuntimeConfig: {
  apiKey: API_KEY,
  apiUrl: API_URL
 }
 ~~~
}

と記述し、.envファイルには

API_KEY = API-keyを入力
API_URL = APIを入力

とすることで$configから取得できます。

ページコンポーネントでデータを取得

vuexに保存したデータを実際にコンポーネントで使っていきます。
その際はgettersを使ってstateの値を取得するのが好ましいです。

↓参考
https://uncle-javascript.com/vuex-getters

import { mapGetters } from "vuex";

export default {
 ~~~
  computed: {
    ...mapGetters(["articles"])
  },
 ~~~
}

これで記事の配列が取得できるので一覧表示に使えます。
詳細ページは

  computed: {
    ...mapGetters(["articles"]),
    article() {
      const currentId = this.$route.params.id;
      return this.articles.find(article => article.url === currentId);
    }
  },

こんな感じで該当の記事情報を取得すればいいと思います。

microCMSに、コンテンツのフィルター機能もあるので(fieldsパラメーター)
そちらを使うのもありだと思います。
今回はgettersでいろいろなパターンのフィルターをしています。

これで実際に投稿した記事のデータを取得できるようになったので、あとは表示させるだけです。

公開する

構築が終わったら、公開作業をしていきます。

今回はnetlifyにアップしてgithubと連携することで自動でCI機能が使えるので
特にデプロイのためにする作業はありません。
(便利すぎますね、、、)

netlifyを使わずに普通のレンタルサーバーにftp接続して自動アップロードさせる仕組みを
github actionで作ったこともあるので、それについてもいずれまとめるかもしれません(希望)。

おわりに

個人的にJamstackな構成はとても理にかなっていて気に入っています。
特にユーザーごとに出し分ける必要がないコンテンツは事前にビルドしておく、
というのが必要最小限にまとまっていて好きですね。

CMS以外にもECもヘッドレスの波が来ているので、これからのJamstackの広がりに期待です。

また、プレビュー画面の作り方についてはネットにあまり記事がないように思うので、
そちらは次あたりでまとめようと思います。

今回作ったブログのコードはgithubに公開しています。
https://github.com/dc7290/d-suke-notebook-blog

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

【DRF + Vue.js 】API取得時のエラー(凡ミス)

はじめに

 Vueのコンポーネント内から、DRFで作成したAPIを以下のようにaxiosなどで取得する際にエラーが発生した。

.../source/views/Mypage.vue
export default {
  ...
  ...
  mounted() {
    this.axios.get("/users/" + this.user_id).then(response => {
      this.Person = response.data
    })
  }
};

発生したエラー

Access to XMLHttpRequest at 'http://127.0.0.1:8000/XXX/XXX/XXX' from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.

VueからのXMLHttpRequestのアクセスがDRF側でブロックされているという感じの内容である。
CORSの設定を確認すると、以下の様にしっかり設定できている。

.../settings.py
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
    'http://localhost:8080',
    'http://127.0.0.1:8080',
)

解決

リクエスト先のURLで最後に '/' をつけるのを忘れていた。
したがって、Vueのコンポーネントファイルを以下の様に修正すればよい。

.../source/views/Mypage.vue
export default {
  ...
  ...
  mounted() {
    // 最後に'/'を追加する
    api.get("/users/" + this.user_id + '/').then(response => {       
      this.Person = response.data
    })
  }
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuetifyのbreakpointのnameはstringだけじゃない

結論

export type BreakpointName = number | 'xs' | 'sm' | 'md' | 'lg' | 'xl'

export interface Breakpoint {
  name: BreakpointName
  // 省略
}

問題提起

vuetify.breakpoint.namestringを指定するとこんなエラーが出ていた

The types of 'name' are incompatible between these types.
  Type 'BreakpointName' is not assignable to type 'string'.
    Type 'number' is not assignable to type 'string'.

stringだけじゃないのか??

Vuetifyの公式ドキュメントを読むとstringが帰ってくるのかと思った。
https://vuetifyjs.com/ja/customization/breakpoints/

解説

Vuetifyのソースコードを読んでみよう

VueitfyのBreakpointの型

今回はブレークポイントのTypesだけ見れればいいので下記のリンクのファイルを確認

  1. どうやらBreakpoint.nameがBreakpointNameらしい
  2. BreakpointNameはnumber | 'xs' | 'sm' | 'md' | 'lg' | 'xl'が入る事がわかる

https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/types/services/breakpoint.d.ts

packages/vuetify/types/services/breakpoint.d.ts
// Types
export type BreakpointName = number | 'xs' | 'sm' | 'md' | 'lg' | 'xl'

// Interfaces
export interface Breakpoint {
  height: number
  lg: boolean
  lgAndDown: boolean
  lgAndUp: boolean
  lgOnly: boolean
  md: boolean
  mdAndDown: boolean
  mdAndUp: boolean
  mdOnly: boolean
  name: BreakpointName
  sm: boolean
  smAndDown: boolean
  smAndUp: boolean
  smOnly: boolean
  width: number
  xl: boolean
  xlOnly: boolean
  xs: boolean
  xsOnly: boolean
  mobile: boolean
  mobileBreakpoint: BreakpointName
  thresholds: BreakpointThresholds
  scrollBarWidth: number
}

export interface BreakpointOptions {
  mobileBreakpoint?: BreakpointName
  scrollBarWidth?: number
  thresholds?: Partial<BreakpointThresholds>
}

export interface BreakpointThresholds {
  xs: number
  sm: number
  md: number
  lg: number
}

なぜBreakpointNameにNumberが入るの?

先程のコードの抜粋を見るとnameとmobileBreakpointにBreakpointNameが使われているらしい。

packages/vuetify/types/services/breakpoint.d.ts
export type BreakpointName = number | 'xs' | 'sm' | 'md' | 'lg' | 'xl'

export interface Breakpoint {
  name: BreakpointName
  mobileBreakpoint: BreakpointName
}

datatableのpropsを例に説明

どうやら下記の様に利用されるらしい。
defaultでは600が入っているが自由に変更できるとのこと。

https://vuetifyjs.com/ja/components/data-tables/

<v-data-table
  :headers="headers"
  :items="desserts"
  :items-per-page="5"
  class="elevation-1"
  mobile-breakpoint="500"
/>

VuetifyのmobileBreakpointが利用されているソース

https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/services/breakpoint/index.ts#L55

また、mobileBreakpointがnumberだけならBreakpointNameで統一せずに分けろよな!!
と思ったがmobileBreakpointmにlgのようstringを使うコードも一応存在するみたい。

breakpoint.nameにnumber入れてるコードあるかな?

ということでVuetifyのbreakpointのテストコードを拝見
https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/services/breakpoint/__tests__/breakpoint.spec.ts

number入れてるテスト無いじゃん!!

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

【Vue.js】Vue CLIで作成したページのtitleとdescriptionを変更する方法

【Vue.js】Vue CLIで作成したページのtitleとdescriptionを変更する方法

Vue CLIで作成したページはデフォルト状態だと、titleはプロジェクト名で全ページ共通。descriptionは存在しない状態になっている。

routerで各ページのtitleとdescriptionを設定できるようにする。

・参考にしたページ
- https://www.sky-limit-future.com/entry/vue_title_desc_tag
- https://router.vuejs.org/ja/guide/essentials/navigation.html
- https://router.vuejs.org/ja/guide/advanced/meta.html

目次

  1. 作業手順
  2. ルートにmetaフィールドを追加
  3. 共通コンポーネントapp.vueにtitleとdescriptionをセットするメソッドを追加
  4. 共通コンポーネントapp.vueにmountedオプションをセット
  5. 共通コンポーネントapp.vueにwatchオプションをセット
  6. 共通テンプレート(pubulic>index.html)にdescription属性を追加
  7. 確認用ソースコード

(参考)Vue CLIによるWEBページの作成方法

Vue CLIでWEBページを作成する方法

作業手順

  1. ルートにmetaフィールドを追加
  2. 共通コンポーネントapp.vueにtitleとdescriptionをセットするメソッドを追加
  3. 共通コンポーネントapp.vueにmountedオプションをセット
  4. 共通コンポーネントapp.vueにwatchオプションをセット
  5. 共通テンプレート(pubulic>index.html)にdescription属性を追加


1. ルートにmetaフィールドを追加

各ルートにmetaフィールドを追加して任意のプロパティをセットすることができる。

これらの情報はrouteインスタンスに格納される。

プロパティ名はわかりやすいように、title->title, description->descとする(任意)。

ファイル

router > index.js
image.png

下記のmeta部分を記述する。

script
  const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { title: 'ページタイトル', desc: 'ディスクリプションを記述' }
  }
]


2. 共通コンポーネントapp.vueにtitleとdescriptionをセットするメソッドを追加

methodsオプションに下記を記述。
プロパティへのアクセス方法はルートインスタンス.meta.設定した変数名

titleをセットするメソッド

  • メソッド名: createTitleDesc
  • 引数でrouteインスタンスを渡す
    • 変数routeInstanceに代入している(変数名は任意)
    • 後述のmountedオプションで代入している
  • route.meta.titleで設定されているtitleを取得
  • if分岐でtitleが設定されていれば、後ろにサイト名をつけた文字列をsetTitlに格納。
  • titleタグ(document.title)にsetTitleを代入する。
App.vue
export default {
  methods : {
    createTitleDesc : function(routeInstance){
         // タイトルを設定
        if(routeInstance.meta.title){
            var setTitle = routeInstance.meta.title + ' | サイト名など(任意)';
            document.title = setTitle;
        } else {
            document.title = 'ルートでtitleがセットされていない場合に表示するテキスト'
        }
}


descriptionをセットするメソッド

基本的にはtitleのメソッドと同じ。

  • descriptionはmetaタグのname属性なので、querySelecterで要素を指定。
  • descriptionの値はcontent属性に記述するためsetAttributionで設定する。
App.vue
export default {
  methods : {
    createTitleDesc : function(routeInstance)
        // メタタグdescription設定
        if(routeInstance.meta.desc){
            var setDesc = routeInstance.meta.desc + ' | MIYACHIN VUE';
            document.querySelector("meta[name='description']").setAttribute('content', setDesc)
        } else {
            document.querySelector("meta[name='description']").setAttribute('content', 'description is not set')
        }
    } 
  }
}

3. 共通コンポーネントapp.vueにmountedオプションをセット

mountedオプションは早い段階で実行(コンポーネントやDOMを読み込むよりも早い)される。

  • routeインスタンス($rounte)を変数(routeInstance)に代入する。
  • 上記で設定したtitleとdescriptionをセットするメソッド(createTitleDesc)を実行する。
App.vue
export default {
   mounted : function(){
      var routeInstance = this.$route;
      this.createTitleDesc(routeInstance);
  }
}

4. 共通コンポーネントapp.vueにwatchオプションをセット

titleやdescriptionに変更があった場合に、変更内容を自動反映するようwatchオプションを追加する。

App.vue
export default {
  watch: { 
      '$route' (routeInstance, from) {
          this.createTitleDesc(routeInstance);
      }
  }
}

App.vueのJSソースコード

上記1〜4を合わせたコードは下記になる。

App.vue
<script>
//routerで設定したタイトルとめたタグを反映する
export default {
  methods : {
    createTitleDesc : function(routeInstance){
         // タイトルを設定
        if(routeInstance.meta.title){
            var setTitle = routeInstance.meta.title + ' | MIYACHIN VUE';
            document.title = setTitle;
        } else {
            document.title = 'title is not set'
        }

        // メタタグdescription設定
        if(routeInstance.meta.desc){
            var setDesc = routeInstance.meta.desc + ' | MIYACHIN VUE';
            document.querySelector("meta[name='description']").setAttribute('content', setDesc)
        } else {
            document.querySelector("meta[name='description']").setAttribute('content', 'description is not set')
        }
    } 
  },
   mounted : function(){
      var routeInstance = this.$route;
      this.createTitleDesc(routeInstance);
  },
  watch: { 
      '$route' (routeInstance, from) {
          this.createTitleDesc(routeInstance);
      }
  }
}
</script>

5. 共通テンプレート(pubulic>index.html)にdescription属性を追加

App.vueファイルでdescriptionを設定するためのmetaタグにアクセスする記述をしたが、デフォルトのhtmlテンプレートにはタグが存在しないため追記する。

publicフォルダのindex.htmlのheadタグ内に記載する。

image.png

.html
<meta name="description">

以下の記述で上記タグを参照することができる。document.querySelector("meta[name='description']")


確認用ソースコード

(1)router > index.js

index.js
Vue.use(VueRouter)

  const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { title: 'ページタイトル', desc: 'ディスクリプションを記述' }
  }
]
})


(2)ルートディレクトリ > App.vue

App.vue
<script>
//routerで設定したタイトルとめたタグを反映する
export default {
  methods : {
    createTitleDesc : function(routeInstance){
         // タイトルを設定
        if(routeInstance.meta.title){
            var setTitle = routeInstance.meta.title + ' | MIYACHIN VUE';
            document.title = setTitle;
        } else {
            document.title = 'title is not set'
        }

        // メタタグdescription設定
        if(routeInstance.meta.desc){
            var setDesc = routeInstance.meta.desc + ' | MIYACHIN VUE';
            document.querySelector("meta[name='description']").setAttribute('content', setDesc)
        } else {
            document.querySelector("meta[name='description']").setAttribute('content', 'description is not set')
        }
    } 
  },
   mounted : function(){
      var routeInstance = this.$route;
      this.createTitleDesc(routeInstance);
  },
  watch: { 
      '$route' (routeInstance, from) {
          this.createTitleDesc(routeInstance);
      }
  }
}
</script>


(3) public > index.html

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">

    <!-- 以下を追加 -->
    <meta name="description">

<link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>



以上。

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

【Nuxt.js】Nuxt実践編:あると便利なコピーボタンの作り方

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

前置き

ここ最近は文法編に力を入れていたので
久しぶりの実践編です✨

Frame 1.png

input内のテキストを
コピーするボタンを作ってみましょう♪
イベントアプリなどでページを作成し、
自動生成されたurlをコピーしたい時なんかに使えます?

urlの自動生成まではやりません。
あくまでinputとコピーボタンのみ作成します??
スタイリングも省いてます、そこはお好みで♪

まずはinputを作成

コピーボタンの作り方は
3通り用意していますが、
input部分はどれも共通です?

divではダメなのか

今回はコピーボタンがあるので
値が見えなくても@clickでコピーさえできればOK⭕️
そのためinputでやっていますが、
urlを直接選択するような場合は
スクロールの効くdivで作成すると良いと思います?

divで作る場合も後述しておきます✍️

解説

readonly属性
urlを間違って編集したりしないようにします⚠️

:value
今回はユーザーが入力する必要がないので
@inputが不要、双方向バインディングが不要です?
値だけバインドできればOK⭕️

class="text"
input要素ではありますが、
入力不要でテキスト表示の役割のため。
コンポーネントにする場合も
InputText.vueなどの命名にし、
$attrsを使うのが良いと思います?

⬇️双方向バインディング、
 v-modelについてはこちら
 https://wp.me/pc9NHC-kI

コード

index.vue
<template>
 <div class="page">
   <input
     id="input"
     :value="url"
     type="text"
     readonly
     class="text"
   >
 </div>
</template>

<script>
export default {
 data () {
   return {
     url: 'https://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb',
   }
 },
}
</script>

コピーボタンを作る

コピーと言えばdocument.execCommand()ですが廃止!
なのでClipboard APIと
nuxt-clipboard2を使ってみましょう?

document.execCommand()も
一応…最後に載せています?

方法1: Clipboard API

? 続きはWPでご覧ください?
https://wp.me/pc9NHC-xv

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

Could not find a declaration file for module 'vee-validate/dist/locale/ja.json'. で怒られる。

結論

types/vee-validate.d.ts
declare module "vee-validate/dist/locale/*";

問題提起

VeeValidateの日本語設定を行っているファイルでこんなエラーが出ました。

English
Could not find a declaration file for module 'vee-validate/dist/locale/ja.json'. 
Japanese
モジュール 'vee-validate/dist/locale/ja.json' が見つかりません。

VeeValidateはTypeScriptで書かれているハズ…

最初はTS非対応のプラグインでよくある@types/{pluginName}を探しました。
しかし、有りません。

それもそのハズ、Veevalidateは既にTypeScriptで書かれています。
https://github.com/logaretm/vee-validate

また、エラーが表示されるのはlocal言語指定しているjsonファイルの呼び出し部分のみです。

nodo_modules以下を見てみる

$ cat node_modules/vee-validate/dist/locale/ja.json

結果はGitHubのこのページと同様のコードが表示されました。
https://github.com/logaretm/vee-validate/blob/master/locale/ja.json

解決

"vee-validate/dist/locale/*"のパスの型をグローバルに宣言してあげます。

types/vee-validate.d.ts
declare module "vee-validate/dist/locale/*";

おわりに

正直なぜ宣言ファイルを見つけられなかったのかも、
Pathに対するグルーバルな型指定でエラー消えるのかも理解できていません。

もし理解された方や他のもっといい方法があるという方がいらっしゃいましたら
コメントにてご指摘頂けると幸いです。

参考

GitHubの問題解決issue
https://github.com/logaretm/vee-validate/issues/1477

Veevalidateの型宣言ファイル
https://github.com/logaretm/vee-validate/blob/master/src/types.ts

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

【Vue.js】Vue CLIとは?ゼロからPJを立ち上げ作成したページを表示する方法

【Vue.js】Vue CLIとは?ゼロからPJを立ち上げ作成したページを表示する方法

開発支援ツールであるVue CLIを使えるようにするための環境構築方法から実際にブラウザに表示するまでの流れ。

目次

  1. Vue CLIとは?
  2. Vue CLIでできること
  3. 開発に必要なもソフトウェア
  4. Node.jsのインストール
    1. Node.jsとは?
    2. NPMとは?
    3. 主なサーバーサイド言語(&OS)とパッケージ管理ツール一覧
    4. Node.jsのインストール実操作(macの場合)
    5. nodeberwとは?
    6. Node.jsのLTSとは?
  5. Vue CLI v3のインストール
    1. npmのインストールコマンドの内容
    2. Vue CLI最新版のインストール方法
  6. 新規プロジェクトの作成
    1. Vue CLIでプロジェクトを作成
    2. Vue CLI対話モード
  7. PJのファイル・ディレクトリ構成
    1. node_modulesフォルダ
    2. publicフォルダ
    3. srcフォルダ
    4. 各種ファイル
    5. package-lock.jsonファイル
  8. serveコマンドの処理の流れ
  9. 単一ファイルコンポーネント
    1. 単一ファイルコンポーネントとは?
    2. 単一ファイルコンポーネントのコード例
    3. 単一ファイルコンポーネントのビルド
    4. ビルドとは?
    5. Vue CLIにおけるビルドの方法
  10. 単一ファイルコンポーネントの作成と表示
    1. 単一ファイルコンポーネントの作成
    2. 作成した単一ファイルコンポーネントの表示


Vue CLIとは?

CLIはCommand Line Interfaceの略。コマンドラインを使って開発を行うためのツール。

言語はVue.jsを使うため、Vue CLIと呼ぶ。Vue.js公式のツール。

Vue CLIでできること

Vue CLIを使うことで下記のようなことができる。
各機能を自分で作ることもできるが労力がかかる。よく使う機能を予め用意してくれているありがたいもの。

  • プロジェクトのテンプレートの作成
  • 複数のjsファイルを一つにまとめる
  • .vueファイルを.jsに変換する
  • トランスパイル
  • JavaScriptの構文チェック
  • テストツールの導入など


トランスパイルとは?

与えられたコードを別の言語に変換すること。プログラミングの翻訳。

JSでは、古い書き方にしか対応していないブラウザ用に、言語を変換することを指す。

<もう少し細かく>
JSは2015年に仕様が大きく変わった。その時に公表されたJSの仕様をES2015と呼ぶ。それ以前の使用がES系。

一部のブラウザでは、このES2015以降の書き方で書かれたJSを読み込めないため変換を行う。これがトランスパイルの主な目的。

トランスパイルの変換ソフト(トランスパイラ)にはBabelを使うのが一般的。

(私感)ES5からES2015になったのが2015年でもう5年以上経過しているので、いい加減気にしなくてもいいのではと思う。

開発に必要なもソフトウェア

  • Node.js v8.9以上
  • Vue CLI 3系のNPMパッケージ
  • テキストエディタ

Node.jsのインストール

Node.jsとパッケージ管理ソフトのNPMをインストールする。
NPMはNode.jsのソフトの中に入っている。

Node.jsとは?

サーバーサイドのJavaScript。サーバー操作をJavaScript言語を用いてできるようにしたのがNode.js。

JSはブラウザ画面上での処理を記述するというのが基本的な使い方。これをサーバーサイドの言語であるPHP、Ruby、Pythonなどの仲間入りさせたのが、Node.js。

NPMとは?

Node Package Managerの略。Nodeで使える便利なパッケージの管理ツール。

パッケージのインストールやバージョンアップなどができる。

主なサーバーサイド言語(&OS)とパッケージ管理ツール一覧

言語 パッケージ管理ソフト
Node.js npm
Ruby gem
Python pip
Linux yum
PHP composer
Mac homebrew
Windows Chocolatey/Windows Package Manager


Node.jsのインストール実操作(macの場合)

#インストール済みか確認(バージョンが表示されればインストール済み)
$ node -v

#npmがインストール済みか確認
$ npm -v

#homebrewがインストール済みか確認
$ brew -v 

#homebrewのインストール(5分ぐらい)
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

==> The Xcode Command Line Tools will be installed.
Press RETURN to continue or any other key to abort // Enterキーをクリックし、PCのパスワードを入力

==> Next steps:
- Run `brew help` to get started //完了


#nodebrewのインストール(10秒ぐらい)
$ brew install nodebrew

#nodebrewのバージョン確認
$ nodebrew -v


#nodebrewでインストールできるnode.jsのバージョン確認
$ nodebrew ls-remote


#node.js用にディレクトリを作成
$ mkdir -p ~/.nodebrew/src


#node.js公式サイトで確認したLTSバージョンをインストール
$ nodebrew install-binary v12.18.3


#nodebrewのバージョンを確認
$ nodebrew ls

v12.18.3
current: none //現在の有効になっているバージョンがない状態


#node.jsの使用するバージョンを指定
$ nodebrew use v12.18.3


use v12.18.3 //設定完了

#node.jsのパスを通す(bashを利用)
echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile


#パスを通した設定を反映
$ source ~/.bash_profile


#node.jsのバージョンを確認
$ node -v

v12.18.3 //指定したバージョンが表示されれば完了

homebrewとは?

Mac OSのパッケージ管理ソフト。

・homebrew公式サイト
https://brew.sh/index_ja.html

nodeberwとは?

node.jsのバージョン管理ソフト。

homebrewでもバージョン管理できるが、古いバージョンをインストールし直した時にパスを通しなおすなど作業が必要。

nodebrewでバージョンを変更すれば、面倒な設定を合わせて実行してくれる。

Node.jsのLTSとは?

LTSとはLong Term Supportの略。
ソフトには最新版と、長期サポート対象が存在する。基本的にインストールはLTSバージョンにする。

node.jsのLTSは公式ページで確認できる。
https://nodejs.org/ja/

image.png


Vue CLI v3のインストール

今回は3系を使うためバージョンを指定してインストール。
(2020年8月時点の最新版はv4.5.1)

#Vue CLIがインストールされているか確認
$ vue --version


#Vue CLIをインストール(約30秒)
npm install -g @vue/cli@3.9.3


#Vue CLIがインストールされているか確認
$ vue --version

npmのインストールコマンドの内容

npm install -g @vue/cli@3.9.3
 ┗ オプション -g:グローバル(どこからでも使える)にする。
 ┗ @vue/cli: 最新版をインストール
 ┗ @vue/cli@バージョン: バージョンを指定してインストール

・Vue CLIのバージョンリリース状況
https://github.com/vuejs/vue-cli/releases

Vue CLI最新版のインストール方法

v3 -> v4へのアップグレードを含む

#最新版のインストール
$ npm install -g @vue/cli@next

#既存プロジェクトをアップデート
$ vue upgrade --next


新規プロジェクトの作成

Vue CLIでプロジェクトを作成

#(必要に応じて)フォルダの作成
$ mkdir ディレクトリパス

#PJフォルダを作成するディレクトリに移動
$ cd ディレクトリパス

#Vue CLIでPJを作成
vue create PJ名 

vue createを実行すると、現在いるディレクトリにPJフォルダが作成される。

Vue CLI対話モード

vue createでPJを作成すると、Vue CLIの対話モードに移る。

(1)確認用のシンプルな設計とするため、Manually Select featuresを選択する。

image.png

(2)PJに必要なパッケージを選択する。
以下3つのパッケージにチェックを入れる。(spaceキーで選択切り替え)

  • Babel:トランスパイラ
  • Router:ルーティングのためのVueルーター
  • Vuex:バージョン管理ソフト
image.png

チェックしてないソフトも、必要になったタイミングで別途インストール可能。

(3)historyモードを使用する(YES)

image.png

(4)ソフトの設定ファイルを個別に管理する(In dedicated config files)

image.png

(5)現在の設定をプリセットとして保存しない(No)

image.png

(6)インストール完了まで待機(約20秒)

(7)PJディレクトリに移動し、npmサーバーを起動する
指示されたコマンドを実行

image.png
$ cd PJ名
$ npm run serve

(7)ページを開く
Localで与えられたURLを開く。
http://localhost:8080/

image.png

ウェルカムページが表示されれば完了

image.png


PJのファイル・ディレクトリ構成

image.png

node_modulesフォルダ

インストールしたライブラリが保存されている。

publicフォルダ

WEB上に公開されるフォルダ。ドキュメントルートとなる。

image.png

srcフォルダ

開発者用のリソースを保存するフォルダ。

image.png

各種ファイル

ファイル名 概要
.browserslistrc babelのサポートするブラウザのバージョンが記述されている。(読み方: .browsers list rc)
.gitignore gitの無視リスト
babel.config.js babelの設定ファイル
package-lock.json npmの依存パッケージの設定
postcss.config.js postcssの設定ファイル

package-lock.jsonファイル

npmのコマンドの対応表や、使用するパッケージ情報が記載されている。

▼コマンド
例えば、npm run serveでnpmを起動したが、実際は、
npm run vue-cli-service serveが実行されている。

image.png

・serve:開発用の環境用のコマンド
・build:本番公開用のコマンド

▼パッケージバージョン情報
・dependencies: 使用するnpmパッケージ
・devDependencies: 開発モードでのみ使用するnpmパッケージ

image.png

serveコマンドの処理の流れ

npm run serveで実行される処理の流れは、

(1)vue-cli-service serveコマンドを実行
"serve": "vue-cli-service serve",

(2)publicフォルダ内をドキュメントルートとして公開

(3)htmlファイルのテンプレートに内容が表示される。

.html
<div id="app"></div>

(4) srcフォルダ内のmain.jsが反映される

.js
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

(5) renderオプションでappコンポーネントを表示する

.js
  render: h => h(App)

(6)appコンポーネントはsrcのapp.vueに記述されている。
app.vueにはHTML, css, jsが記述されており、その内容をテンプレートに表示している。

image.png


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

単一ファイルコンポーネントとは?

拡張子が「.vue」のファイルを単一ファイルコンポーネントと呼ぶ。vue.jsのコンポーネントを個別のファイルに切り出したもの。

html, css, jsを記述することが可能。

単一ファイルコンポーネントのコード例

.vue
<template>
  <div id="app">
   <!-- htmlタグを記述 -->
  </div>
</template>

<style>
/*cssを記述*/
</style>

<script>
//jsを記述
</script>

単一ファイルコンポーネントのビルド

ビルドとは?

ソースコードのバグ検証後に実行可能なファイルに変換すること。

ブラウザはvueファイルを読み込めないため、ブラウザに表示するにはビルドが必要になる。

Vue CLIにおけるビルドの方法

(1)Webpack と (2)Vue-Loaderを使う

webpackとは?
自分で作成したスクリプトとライブラリとして読み込んでいるスクリプトを合体させるソフトウェア。

Vue-Loaderとは?
vueコンポーネントをjsファイルに変換する。
Webpackのローダー。


単一ファイルコンポーネントの作成と表示

単一ファイルコンポーネントの作成

こんにちは! と表示するファイルを作成する。

(1)srcフォルダのcomponentsフォルダの中に、konnichiwa.vueファイルを作成

image.png

(2)templateを作成する。
- HMTL:templateタグで囲む
- style: scoped属性をつけると現在のテンプレートにのみ適用となる

.vue
<template>
    <div>
        <h1>こんにちは!</h1>
        新たにテンプレートを作成し読み込み成功
        <hr>
    </div>
</template>

<script>
</script>

<style scoped>
</style>

作成した単一ファイルコンポーネントの表示

Home.vue内で作成したファイルの公開を行う場合。

(1)srcフォルダ > Viewsフォルダ > Home.vueファイルを開く

image.png

(2)vueファイルをインポートする
import コンポーネント名 from '@/components/ファイル名'

Home.vue
import Konnichiwa from '@/components/Konnichiwa.vue'



(3) componentsオプションに設定したコンポーネント名を追記

Home.vue
export default {
  name: 'Home',
  components: {
    HelloWorld,
    Konnichiwa
  }



(4) templateタグ内にコンポーネント名を追記
<コンポーネント名/>

Home.vue
<template>
  <div class="home">
    <Konnichiwa/>
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>



(5)サーバーを起動
npm run serve

▼読み込みに成功
http://localhost:8080/

image.png



(5)サーバーの終了
ctrl + c



以上。

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

VueとFirebaseでWebアプリ開発でハマったこと②: javascriptやnpmなどのエラー

前回『VueとFirebaseでWebアプリ開発でハマったこと①: Strings must use singlequote, Extra semicolon』の続き。

概要

firestoreからデータを受け取ってVueで表示し、Vueでデータを送信してfirestoreに保存する一連の流れをInputしたいと思い、『firestore, vue.jsでリアルタイム同期のチャットを実装してみる [チュートリアル形式]』という記事を参考に作業していたところ次の3つのエラーにハマったのでどう解決したのかまとめる。

エラー1: Uncaught TypeError: Cannot read property 'install' of undefined

 スクリーンショット 2020-08-07 12.29.44.png

上記は、$ yarn serveの結果は正常だけどもアクセス後にエラーになるケースでした。
このエラーだけでは状況がわからないので、エラー文3行目に対応箇所であったfirebase.jsが表示されているためこのリンクをクリックするとConsole上にそのソースコードとエラーの箇所まで飛んでくれました。

原因としては、VueがFirebaseの機能を扱う初期化で問題が起きていることが分かります。
ググったところvuefireのissue『App startup failure with VueFire plugin #286』を見つけ、次のように対応するとエラーが解消できました。

- import VueFire from 'vuefire'
- Vue.use(VueFire)

+ import { firestorePlugin } from 'vuefire'
+ Vue.use(firestorePlugin)

エラー2: [Vue warn]: Error in beforeCreate hook: "Error: Vuetify is not properly initialized, see https://vuetifyjs.com/ja/getting-started/quick-start/#bootstrapping-the-vuetify-object"

スクリーンショット 2020-08-07 12.32.26.png

正確には他にも複数エラーが出てますが、エラーの解決は基本的に最初のエラーから対応するべきなのでこのエラーに集中しました。

原因は、参考にした記事のプラグインのインストールにあたるところで、ただプラグインを入れるだけ解説されてますが、結局はこれだけでは不十分でちゃんとvuefireのインストール対応が必要でした。

エラー文にも出ている通り、公式のvuefireのクイックスタートにアクセスして「Nuxtのインストール」「Webpackのインストール」の対応が必須でした。

エラー3: These dependencies were not found

$ yarn serve

...

ERROR  Failed to compile with 3 errors                                                                                                                                                                                               12:48:50

These dependencies were not found:

* firebase/app in ./src/plugins/firebase.js
* firebase/firestore in ./src/plugins/firebase.js
* vuefire in ./src/plugins/firebase.js

To install them, you can run: npm install --save firebase/app firebase/firestore vuefire

色々触っているとたまにこのエラーが出てたんですが、これは単に./node_modulesを削除して再度$ npm installすると直りました。

 

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

VueとFirebaseでWebアプリ開発でハマったこと②:

前回『VueとFirebaseでWebアプリ開発でハマったこと①: Strings must use singlequote, Extra semicolon』の続き。

概要

firestoreからデータを受け取ってVueで表示し、Vueでデータを送信してfirestoreに保存する一連の流れをInputしたいと思い、『firestore, vue.jsでリアルタイム同期のチャットを実装してみる [チュートリアル形式]』という記事を参考に作業していたところ次の3つのエラーにハマったのでどう解決したのかまとめる。

エラー1: Uncaught TypeError: Cannot read property 'install' of undefined

 スクリーンショット 2020-08-07 12.29.44.png

上記は、$ yarn serveの結果は正常だけどもアクセス後にエラーになるケースでした。
このエラーだけでは状況がわからないので、エラー文3行目に対応箇所であったfirebase.jsが表示されているためこのリンクをクリックするとConsole上にそのソースコードとエラーの箇所まで飛んでくれました。

原因としては、VueがFirebaseの機能を扱う初期化で問題が起きていることが分かります。
ググったところvuefireのissue『App startup failure with VueFire plugin #286』を見つけ、次のように対応するとエラーが解消できました。

- import VueFire from 'vuefire'
- Vue.use(VueFire)

+ import { firestorePlugin } from 'vuefire'
+ Vue.use(firestorePlugin)

エラー2: [Vue warn]: Error in beforeCreate hook: "Error: Vuetify is not properly initialized, see https://vuetifyjs.com/ja/getting-started/quick-start/#bootstrapping-the-vuetify-object"

スクリーンショット 2020-08-07 12.32.26.png

正確には他にも複数エラーが出てますが、エラーの解決は基本的に最初のエラーから対応するべきなのでこのエラーに集中しました。

原因は、参考にした記事のプラグインのインストールにあたるところで、ただプラグインを入れるだけ解説されてますが、結局はこれだけでは不十分でちゃんとvuefireのインストール対応が必要でした。

エラー文にも出ている通り、公式のvuefireのクイックスタートにアクセスして「Nuxtのインストール」「Webpackのインストール」の対応が必須でした。

エラー3: These dependencies were not found

$ yarn serve

...

ERROR  Failed to compile with 3 errors                                                                                                                                                                                               12:48:50

These dependencies were not found:

* firebase/app in ./src/plugins/firebase.js
* firebase/firestore in ./src/plugins/firebase.js
* vuefire in ./src/plugins/firebase.js

To install them, you can run: npm install --save firebase/app firebase/firestore vuefire

色々触っているとたまにこのエラーが出てたんですが、これは単に./node_modulesを削除して再度$ npm installすると直りました。

 

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

Vue.jsでaxiosを使ってぐるなびAPIを取得する

環境

macOS Catalina 10.15.6
vue 2.6.11
vue/cli 4.4.6

ぐるなびAPIを取得

こちらのページから新規アカウントを発行しアクセスキーを取得します。
https://api.gnavi.co.jp/api/

登録したメールアドレスにアクセスキーが送られてくるのでそれを使います。
ぐるなび WEB SERVICEのAPIテストツールを使うとわかりやすいです。

keyidに先ほど取得したアクセスキーを入力し、取得したいパラメータを[追加]を押して選びます。

URLの欄に入っているURLを実際のプログラムでも使います。

クエリを送信を押すと取得できる情報がjson形式で返ってきます。

axiosをインストール

$ npm install axios --save

無事にインストールが完了するとpackege.jsonに"axios"が追加されているので任意のvueファイルでaxiosを読み込む

import "axios"

const axios = require('axios').default;

    async mounted(input){
      try{
        const response = await axios.get(APIテストツールで生成したURL", {
            params: {
              freeword:”居酒屋”
            }})
        this.info= res.data.rest;
      }catch(error){
        alert(error.message)
      }
  }

axios.get()の引数にAPIテストツールで生成したURL渡す。

URLの末尾にparams:{}を追記して取得したい情報を選択します。
今回はfreewordをキーとしています。
他にも色々種類があるので詳しく知りたい方はこちらをご覧ください。
https://api.gnavi.co.jp/api/manual/foreignrestsearch/

今回はfreeword直接検索ワードを渡していますが、実際はv-modelでフォームから取得した値を渡してあげるのが一般的かと思います。

これで変数responseの中に取得した情報が入っているので好きな様に加工して表示する形になります。

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

VeeValidateを使ってVueアプリでバリデーションをする

VeeValidate

VeeValidateは以下の特徴を備えたVue.jsのバリデーションライブラリーです。
ドキュメントを読みつつ訳しながらまとめた内容です。

https://logaretm.github.io/vee-validate/

  • テンプレートベースのバリデーション
  • 多彩なバリデーションルールがデフォルトで用意済み
  • 一級のローカライゼーションサポート
  • HTML5のinputフォームとカスタムコンポーネントのバリデーションが可能
  • カスタムルールとエラーメッセージも作成可能

はじめよう

利用方法

まずVueのインスタンスに登録します。

import Vue from 'vue';
import VeeValidate from 'vee-validate';

Vue.use(VeeValidate);

コンフィグ設定したいならこちらの方法。

import Vue from 'vue';
import VeeValidate from 'vee-validate';

const config = {
  // config that you want
}

Vue.use(VeeValidate, config);

ベーシックな例

inputフォームにv-validateディレクティブとname属性を追加するだけでOKです。name属性はエラーメッセージの生成に利用されます。
v-validateディレクティブにはルールを設定します。パイプ|を間に挟むことで複数のルールが適用可能です。
以下は「必須、かつ、eメールの形式であること」の例です。

<input v-validate="'required|email'" name="email" type="text">

エラーメッセージを表示するにはerrors.firstメソッドを利用することでその項目の最初のエラーが取得できます:

<span>{{ errors.first('email') }}</span>

Tips
v-validateに定義された値はシングルクォートで囲われています。これはディレクティブに与えられた内容はJavaScriptとして評価されるためです。
シングルクォートで囲むことでString型であることを明示的にする必要があります。
v-validate="required"としてしまうとrequiredというおそらく存在しないであろう変数や関数を参照しようとしてしまいます。

シンタックス

バリデーションルールはString型での定義や、プログラマブルにしたい時はObject型での定義も可能です。

// String
const single = 'required'
const multiple = 'required|numeric'

// Object
const single = { required: true }
const multiple = {
  require: true,
  numeric: true
}

ルールのパラメーター

ルールの一部はパラメーターを持つことができます。パラメーターは複数の定義方法が可能です。

  • String型ならカンマ区切りの値
  • Object型なら配列の値
  • Object型でより複雑なパラメーターならオブジェクト
// String型のパラメーター
const someRule = 'included:1,2,3,4';

// Array型のパラメーター
const someRuleObj = { included: [1, 2, 3, 4] };

// Object型のパラメーター
const someCplxObj = {
  email: {
    allow_utf8_local_part: true
  }
};

ルールの実例

上の内容を実例でみてみましょう。

  • 必須のEメールの入力欄
<input v-validate="'required|email'" type="email" name="email">
<input v-validate="{ required: true, email: true }" type="email" name="email">
  • 必須でないユーザー名の入力欄
<input v-validate="'alpha'" type="text" name="username">
<input v-validate="{ alpha: true }" type="text" name="username">
  • 必須かつ最低6文字のパスワード入力欄
<input v-validate="'required|min:6'" type="password" name="password">
<input v-validate="{ required: true, min: 6 }" type="password" name="password">

エラーの表示

エラーメッセージが生成されると、エラー表示簡易化のために用意されたErrorBagインスタンスに保存されます。

デフォルトではErrorBagのインスタンスはerrorという名前で各コンポーネントのcomputedプロパティにインジェクトされます。
errorという名前は競合を避けるためにコンフィグからカスタマイズ可能です。

エラーメッセージを1つ表示する

一般的には1つのフォームに対して1つのエラーを表示することが多いと思います。これはerrors.first('fieldName')メソッドでエラーを取得可能です。

<input type="text" name="fieldName" v-validate="'required'">
<span>{{ errors.first('fieldName') }}</span>

Tips
VeeValidateのデフォルトでは1フィールドに対して1エラーしか生成しません。これはバリデーションパイプラインの高速化が理由です。
複数のバリデーションエラーのメッセージを同時に生成したい場合はfastExitオプションをコンフィグからカスタマイズしてください。
あるいは、特定のフォームだけに適用したい場合はcontinuesmodifilerの適用も可能です。v-validate.continues="'required|numeric'"

エラーメッセージを複数表示する

1つのフォームに複数のエラーを表示したい場合はerrors.collect('fieldName')メソッドが利用できます。
このメソッドは1つのフィールドに紐づくエラーメッセージを配列で返します。

<input type="text" name="fieldName" v-validate.continues="'required|alpha|min:5'">
<ul>
  <li v-for="error in errors.collect('fieldName')">{{ error }}</li>
</ul>

全てのエラーメッセージを表示する

errors.all()を使いフラットなエラーメッセージの配列を取得できます。

<input type="text" name="first" v-validate.continues="'required|alpha|min:5'">

<input type="text" name="second" v-validate.continues="'required|alpha|min:5'">

<ul>
  <li v-for="error in errors.all()">{{ error }}</li>
</ul>

フィールド名ごとにグルーピングしたい場合は、errors.collect()を引数無しで利用します。
キーがフィールド名で値がエラーメッセージの配列のオブジェクトが取得できます。

<input type="text" name="first" v-validate.continues="'required|alpha|min:5'">

<input type="text" name="second" v-validate.continues="'required|alpha|min:5'">

<ul>
  <li v-for="group in errors.collect()">
    <ul>
      <li v-for="error in group">{{ error }}</li>
    </ul>
  </li>
</ul>

バリデーションルール

いっぱいあるよ。

https://baianat.github.io/vee-validate/guide/rules.html

カスタムコンポーネントのバリデーション

VeeValidateはHTML5のinput要素だけでなくカスタムコンポーネントのバリデーションも対応しています。
カスタムコンポーネントでの利用はいくつか注意が必要です。このトピックではその注意点について説明します。

どう動くか

カスタムコンポーネントを入力欄として動作させるのにv-modelを利用して値を扱うことは一般的です。
VeeValidateはこの値を$watch APIを利用して監視していますが、$watchには制限があります。
たとえば、v-forのイテレーターの値はv-forのループのコンテキストとしてしか存在していないため監視できません。

VeeValidateはvalueのpropを監視するようフォールバックします。
もしコンポーネントがmodelコンストラクタをカスタマイズしている場合でも、正しいプロパティを検出しそれを監視します。

このデモはVeeValidateとVuetifyのカスタムコンポーネントを組み合わせた例です。

以上はほとんどのケースをカバーしますが、v-modelを利用して値を取り回していない場合は対応できません。
そのようなケースではコンポーネントのコンストラクターオプションを利用することでより細かなバリデーションの挙動を設定することができます。

コンポーネントのコンストラクターオプション

バリデーターのカスタムコンポーネントへの挙動をカスタマイズすることにより不要な属性の追加などを防ぐことができます。
これは$_veeValidateフィールドをカスタムコンポーネントに定義することで実現します:

export default {
  // ...
  data: () => ({
    // コンポーネント内部のデータ
    innerValue: 'initial'
  })
  $_veeValidate: {
    // バリデーションの対象とするデータ
    value () {
      return this.innerValue;
    }
  },
}

$_veeValidateではvalue含めて以下が設定可能です。

プロパティ 種類 初期値 説明
name () =>, string undefined エラーメッセージの生成に使われる名前
value () =>, any undefined バリデーションの対象となる値
rejectFalse boolean false falserequiredに対してバリデーションエラーとなるか
events string `input blur`
validator string new or null コンポーネントがバリデータのインスタンスをどう得るか。newの場合は新しいバリデーターインスタンスを生成して利用する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】Computed, Methods, Watchの使い分けをざっくりと

初学時にちがいがわからなかったこの3つをざっくりと分別すると、

Computed

リアクティブに再評価される
必ずreturnする必要がある

Watch

特定のプロパティの変更によって処理を行いたい時に使う

Method

function全般
必ずしもreturnする必要はない

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

Vue.jsでcssのカスタムプロパティーを使って背景画像を動的に変更する

概要

vue.jsでコンポーネントを作成した際に、cssで指定する背景画像を指定したいことってあると思うんですが、その方法についてハマったので共有します。

作ったもの

※ compostionAPIを使っていますが、必要に合わせてお使いのものにしてください。

<template>
  <section :style="styles">
   <h2>タイトル的な何か</h2>     // ここで使う背景画像を動的に変更したいとする
  </section>
</template>

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

type ContentType = 'hoge' | 'fuga'

const iconUrl: { [key in ContentType]: NodeRequire } = {
  hoge: require('@/assets/images/hoge.png'),
  fuga: require('@/assets/images/fuga.png')
}

type Props = {
  contentType: ContentType
}
export default defineComponent({
  name: 'custom-component',
  props: {
    contentType: {
      type: String as PropType<ContentType>,
      required: true
    }
  },
  setup(props: Props) {
    const styles = computed(() => {
      return {
        '--url': `url(${iconUrl[props.contentType]})`
      }
    })
    return {
      styles
    }
  }
})
</script>

<style lang="scss" scoped>
 section {
   h2 {
     background: var(--url)
   }
 }
</style>

説明

  • Vue.jsでCSSのカスタムプロパティを動的に追加するにはコンポーネントに <:style="style"> でデータを渡して上げることで使えます。
    • こうすることで root.element の中にカスタムプロパティが登録されます。
  • 画像データのパスはそのまま渡しても、assetsのデータはバイナリで展開されるので、 require('画像パス') で読み込んだ状態のものを url() に貼り付けた状態のものを渡して上げることでCSS内で動的に画像が変更されます。
    • ここが結構トラップなので注意

使う時

<custom-component contentType="hoge"/>

or

<custom-component contentType="fuga"/>

使うときは、contentType に指定したtypeを指定してあげるだけです。
※ ここのtypeはunionで指定していますが、コンパイルエラーにはならないけど...

さいごに

画像が展開されたものになっていることを忘れていて、結構時間がかかってしまいました。
普段CSSをメインに触っていないので、 url() がどんな動作をしているのかとか理解が少なかったのでなれないことをやると時間かかりますね。

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

正式リリース前に総予習!! Vue3の変更点まとめ

8月上旬に正式リリース予定とされているVue3の変更点をいち早く理解できるように概要をまとめてみました。それぞれの項目ごとにvuejs/rfc又はVue3 Documentへのリンクを貼っているので索引的に使ってもらえると嬉しいです。

この記事は以下バージョン時点の情報です。

Composition APIの追加

おそらく一番大きな目玉となる変更はこちら。Composition APIという新しいAPIが追加されます(PluginでVue2系でも使用可能です)。
Composition APIはコンポーネントのロジックの柔軟な再構成を可能にする関数ベースのAPI群です。
なお、Vue2系のOptions APIのサポートも継続されるので、Vue2系からのVue3へのバージョンアップ時にComposition APIに書き換える必要はありません。

<!-- Vue3 Composition API -->
<template>
  <form>
    <input type="text" v-model="formState.name" />
    <input type="text" v-model="formState.value" />
    <p>{{ contentLength }}</p>
    <button @click="submit" type="submit">submit</button>
  </form>
</template>

<script lang="ts">
import { defineComponent, computed, ref, onMounted, reactive } from "vue";

export default defineComponent({
  props: {
    maxContentLength: {
      type: String,
      required: true
    }
  },
  setup(props, context) {
    // reactive data
    const formState = reactive({
      name: '',
      content: ''
    })

    // computed
    const contentLength = computed(() => `${formState.content.length} / ${props.maxContentLength}`)

    // emit
    const submit = () => context.emit('submit', formState)

    // lifecycle hook
    onMounted(() => {
      // ....
    })

    return {
      formState,
      contentLength,
      submit
    };
  }
});
</script>

Composition APIについてはこちらの記事でより詳細にまとめています。

先取りVue 3.x !! Composition API を試してみる - Qiita
Vue Composition APIで使えるリアクティブ関連のAPI一覧 - Qiita

Teleportの追加

Teleportは定義したコンポーネントが属するDOMツリーとは別の場所に、まるでテレポートしたかのようにコンポーネントを移動できる機能です。Vue2系でもサードパーティのプラグインLinusBorg/portal-vueで実現されていました。

以下のようにモーダル表示をハンドルするコンポーネントで<teleport>を使うと、toで指定したDOM要素に内部のコンポーネントを移動できます。

<template>
  <div class="container">
    <button @click="toggleModal">toggle modal</button>
    <teleport to="#teleport-target">
      <MyModal v-if="isVisible"/>
    </teleport>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const isVisible = ref(false);

    const toggleModal = () => {
      isVisible.value = !isVisible.value; };
    return {
      isVisible,
      toggleModal
    };
  }
});
</script>

toで指定できるDOM要素は自身のVueインスタンスをマウントしたDOM要素以外も指定できます。

<html lang="ja">
<body>
  <div id="app"></div> <!-- VueがマウントされるDOM -->
  <div id="teleport-target"></div> <!-- teleportで指定されているDOM。ここにMyModalが表示される-->
  <script src="/dist/main.js"></script>
</body>
</html>

teleportを利用すると、いままでCSSのz-indexで調整していたDOMの重なり順制御を、宣言的に制御できるようになりz-indexの指定に悩まされることはなくなります。
Vue3を入れたらすぐに使いたいと思える実用的な機能ですね。

こちらの記事でも詳細をまとめています。
Vue.js 3.0 の新機能を試す。 〜 Teleport 編〜 - Qiita

Fragmentsの追加

FragmentsはVue2系では実現できなかったmulti-root nodeのコンポーネントを可能にするものです。
Vue2系ではコンポーネントのroot要素は必ず1つという制約がありました。

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

Vue3ではFragmentsのおかげでその制約がなくなり、root要素に複数の要素を記載できます。冗長なdivでのラップなどが不要になります。簡潔で良いですね。

<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

ただ、注意点としてコンポーネントに直接Class指定をしていると、今まで自動的にroot要素にClassを当てられたのですが、それがfragmentsの場合は明示的な指定が必要になるというのがあります。

Suspenseの追加(実験的)

Suspenseは非同期処理が解決されるまでフォールバックコンテンツ(例えばLoading中アイコン)を表示してくれる特別なコンポーネントです。いままで、v-if="loading === true" などの状態変数を使って制御していたものを、状態変数を使わずに簡潔に書くことができます。

Suspenseはまだ実験的な機能で、Vue3.0の段階では仕様が確定しておらず今後変更が入ることも考えられます。Vue 3.1での正式リリースを見越しているとのことです。

<template>
  <Suspense>
    <template #default>
      <AsyncComponents/> <!-- 非同期処理を実行するコンポーネント -->
    </template>
    <template #fallback>
      loading...
    </template>
  </Suspense>
</template>

Suspenseの詳細はこちらにまとめています。
Vue.js 3.0 の新機能を試す。 〜 Suspense 編〜 - Qiita

v-modelの仕様変更

v-modelの機能が拡張され、ひとつの要素に複数のv-modelの設定が可能になります。
Vue2系のv-bind.syncで実現していたようなことがv-modelで実現できるようになるイメージですね。この機能によってv-bind.syncは代替されるので利用できなくなります。

以下v-modelの複数バインドの例です。
v-model:xxxxのxxxのところで明示的にバインドするプロパティを指定しています。

<!-- 親コンポーネント -->
<template>
  <UserForm
   v-model:name="name"
   v-model:message="message"
  />
</template>
<!-- UserFormコンポーネント -->
<template>
  <form>
   <label>name</label>
   <input 
    type="text"
    @input="$emit('update:name', $event.target.value)"
   >
   <label>message</label>
   <input 
    type="text"
    @input="$emit('update:message', $event.target.value)"
   >
  </form>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    name: {
      type: String,
      default: ''
    },
    age: {
      type: String,
      default: ''
    }
  },
  setup() { return {} }
})
</script>

また、modifiersの機能が拡張され、.lazy, .number, trimのデフォルトのmodifiers以外に独自に,modifiersを定義できるようになりました。
以下はcapitalizeというmodifiersを設定している例です。v-model:name.capitalize=とすることで入力値にcapitalizeを実行しています。

<!-- 親コンポーネント -->
<template>
  <form>
    <MyName  v-model:name.capitalize="form.name"/>
    <!-- ... -->
  </form>
</template>
<!-- MyNameコンポーネント -->
<template>
  <input
    :value="name"
    @input="updateName"
    type="text"
    name="name"
  />
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    name: {
      type: String,
      default: ''
    },
    nameModifiers: {
      default: () => ({})
    }
  },
  setup (props, { emit }) {
    const updateName = event => {
      let val = event.target.value
      if (props.nameModifiers.capitalize) {
        val = val.charAt(0).toUpperCase() + val.slice(1)
      }

      emit('update:name', val)
    }

    return {
      updateName
    }
  }
}
</script>

scoped CSSの仕様変更

scoped CSSで使える擬似クラスの仕様変更・機能追加が行われます。
まず1つ目が::v-deep()でこれは、今まで/deep/として設定していた、子コンポーネントにスタイルを適用するための擬似クラスです。
/deep/の記法がDEPRECATEDとなり、新たに::v-deep()が追加されています。

::v-deep(.foo) {}
/* コンパイル後 */
[v-data-xxxxxxx] .foo {}

2つ目が、::v-slotted()で、これはslotで受け取った親コンポーネントの要素にスタイルを適用するためのものです。Vue3からデフォルトではslotで受け取った要素は、子コンポーネントのスタイルが適用されなくなるため、こちらが追加されました。

::v-slotted(.foo) {}
/* コンパイル後 */
.foo[v-data-xxxxxxx-s] {}

最後が、::v-global()で、これはscoped CSSの中でグローバルなスタイルの定義を可能とするものです。この擬似要素を設定したクラスには[v-data-xxxx]が付与されないため、Scopedではなくグローバルに適応されます。

::v-global(.foo) {}
.foo {}

$attrsの仕様変更

$attrsの機能が拡張され、今まで$listenersで参照していたネイティブイベントや別管理であったclass、styleも全て包括して$attrsから取得できるようになりました。

<MyButton 
  @click="handleClick" 
  @custom="handleCustom" 
  v-model="value"
  type="button"
  class="btn"
/>
// MyButton.vue
$attrs: {
  class: 'btn'
  type: 'button',
  onClick: handleClick,
  onCustom: handleCustom,
  'onUpdate:modelValue': () => { value = payload },
}

そのため、$listenersはDEPRECATEDとなります。(実行時に警告がでます)
$listenersを使って透過的なラッパーコンポーネントを作っていた場合は、$listenersを削除し$attrsへの書き換えが必要です。

<!-- Vue2系 -->
<input v-bind="$attrs" v-on="$liseners" />
<!-- Vue3 -->
<input v-bind="$attrs" />

Global APIの仕様変更

Vueの初期化処理が変わります。createAppでインスタンスを生成し、そのインスタンススコープでusemixinなどが適応されるようになります。
これによってテストケースでのグローバル設定の汚染を防いだり、同一ファイル内で別設定のVueインスタンスの初期化が容易になります。

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App) // インスタンスを生成

app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */) // インスタンススコープで設定される
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

app.config.globalProperties.customProperty = () => {}

app.mount(App, '#app')

filterの廃止

filterは、パイプ演算子|を使って関数適応をlinuxの標準入出力のパイプラインするものですがJavaScriptにはない独自構文で学習コストが増加する、構文解析を複雑にするという理由で削除されます。

filterの処理は通常の関数適応で代替できます。

<!-- filter形式 -->
<p>{{ msg | uppercase | reverse }}</p>

<!-- 通常の関数適応形式 -->
<p>{{ reverse(uppercase(msg)) }}</p>

他の代替方法についてはrfcs/0015-remove-filters.md#alternativesをご覧ください。

Event Emitter系のAPIの廃止

Event Emitter系のAPI($on, $off, $once)が削除されます。
グローバルなイベント管理としてEvent Emitterを仕様している場合は書き換えが必要です。
Event管理の代替にはMittなどの別のEvent Emitterライブラリの利用が推奨されています。

Functional Componentの廃止

Functional Componentはdata等の状態を持たない代わりにレンダリングパフォーマンスの大幅な改善が行えるコンポーネントです。
Vue3ではFunctional Componentを作る際に使用するfunctional: trueのオプションや、<template functional>が削除されます。

Vue3では、通常のコンポーネントとFunctional Componentの性能差は大幅に縮小され、ほとんどのユースケースでは取るに足らないものとなったそうです。

SFC上でfunctionalを使用している場合は、functionalの属性を削除し、attrs、listnersの参照方法の修正が必要です。

<!-- Vue2 functional component -->
<template functional>
  <component
    :is="`h${props.level}`"
    v-bind="attrs"
    v-on="listeners"
  />
</template>

<script>
export default {
  props: ['level']
}
</script>
<!-- Vue3 -->
<template> <!-- functionalを削除 -->
  <component
    v-bind:is="`h${props.level}`"
    v-bind="$attrs" <!-- attrs及びlistnersは$attrsにまとめられる  -->
  />
</template>

<script>
export default {
  props: ['level']
}
</script>

終わりに

以上「正式リリース前に総予習!! Vue3の変更点まとめ」でした。
色々ワクワクするような新機能が追加されていてVue3のリリースが待ち通しいですね。
他にも諸々細かい点が変わってるのでより詳細な変更はvuejs/rfcをご確認ください。

参考

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

Vue学習

FireBase便利そうやね。。
色んな展開の仕方があると思うけどAWSと同じで従量課金やから注意やね。

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