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

【Vue.js】Vue.jsの基本的な使い方まとめ【入門】

はじめに

jQueryガンギマリボーイから脱却するために学習を始めました。

目標はVue.jsとBaaSでオリジナルのポートフォリオを作る事。

「あの人絶対Vueキメてるよね」と言われるようになるまでレッツトライ

「Vueやりたいんだよねぇ〜」って人も僕と一緒にレッツトライ

HTML、CSS、JavaScriptの基礎知識がある程度必要になってきます。

Vueインスタンスの作成

elオプションでVueインスタンスを展開する場所を指定する。

#appid="app"を指定したことになる。

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

ルートテンプレートの作成

HTMLでVueインスタンスを展開する場所を指定する。

HTMLにVueを埋め込むイメージ。

<div id="app">
  <!--ここにVueインスタンスが展開される -->
</div>

テキストのデータバインディング

Vueインスタンスに設定した値はマスタッシュ構文で展開する。

マスタッシュ構文例
{{ message }}

Vueインスタンスのdataオプションmessageの値を設定する

var app = new Vue({
  el: '#app', // マウントするセレクタの指定
  data: {
    message: 'Hello Vue.js!'
  }
})

設定したmessageelオプションに定義したid=appの中で展開できる。

<div id="app">
  <p>
    {{ message }} <!-- Hello Vue.js! -->
  </p>
</div>

id=appの外では展開できない

<div id="app">
</div>
{{ message }} <!-- {{ message }} -->

dataオプションにオブジェクトや配列を設定

カンマで続けて書く事ができる。

var app = new Vue({
  el: '#app',
  data: {
    // userオブジェクトを定義
    user: {
      lastName: 'Yamada',
      firstName: 'Taro',
      prefecture: 'Tokyo'
    },
    // 配列を定義
    colors: ['Red', 'Green', 'Blue']
  }
})

オブジェクトはオブジェクト名.キー名

配列は配列名[数値]でアクセス出来ます。

<div id="app">
  <p>
    {{ user.prefecture }} <!-- Tokyo -->
  </p>
  <p>
    {{ colors[0] }} <!-- Red -->
  </p>
</div>

属性のデータバインディング v-bind

属性のデータバインディングにはv-bindディレクティブを使用する。

<div id="app">
  <input type="text" v-bind:value="message" />
</div>

条件分岐 v-if v-show

v-ifの場合、要素はDOMレベル削除される。
頻繁に表示・非表示を繰り返す場合、描画コストが高くなる。

<div id="app">
  <p v-if="toggle"> <!-- 表示されない -->
    Hello
  </p>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    toggle: false
  }
})
</script>

v-showの場合、条件を満たさない時はdisplay: noneで非表示させる。

表示を頻繁に切り替える際はv-ifと比べて描画コストの点で有利になる。

<div id="app">
  <p v-show="toggle">
    Hello
  </p>
</div>

繰り返し処理 v-for

v-forディレクティブは繰り返し処理を行う事ができる。

試しにdataオプションに配列colorsを定義する。

var app = new Vue({
  el: '#app',
  data: {
    colors: ['Red', 'Green', 'Blue']
  }
})

v-forを使って配列の値を一つずつ取り出して繰り返し処理を行う。

配列名は複数形、繰り返し処理で使う値は単数形にしておくとコードが読みやすくなる。

<div id="app">
  <ol>
    <li v-for="color in colors">
    {{ color }
  </li>
  </ol>
</div>
結果
<div id="app">
  <ol>
    <li>Red</li>
    <li>Green</li>
    <li>Blue</li>
  </ol>
</div>

オブジェクトを繰り返し処理する

dataオプションにuserオブジェクトを定義する

var app = new Vue({
  el: '#app',
  data: {
    user: {
      firstName: 'Daiki',
      LastName: 'Tsuneta',
      age: 28
    }
  }
})

value in オブジェクト名でオブジェクトの中身を一つずつ取り出して繰り返し処理を行う

valueは任意の値を設定できる。vでも動く。

<div id="app">
  <ul>
    <li v-for="value in user">{{ value }}</li>
  </ul>
</div>
結果
<div id="app">
  <ul>
    <li>Daiki</li>
    <li>Tsuneta</li>
    <li>28</li>
  </ul>
</div>

オブジェクトのキーも一緒に表示させたい時はv-for="(値, キー) in オブジェクト名"で表示させる事ができる。

第一引数が値第二引数がキーになっているので注意。

<ul>
  <li v-for="(value, key) in user">
    {{ key }} : {{ value }}
  </li>
</ul>

イベント処理 v-on

v-onディレクティブはイベント処理の基本。

ボタンをクリックしたら現在時刻を表示させるプログラムを書いてみる。

<div id="app">
  <!-- クリックするとonclickメソッドが呼び出される -->
  <button v-on:click="onclick">
    Click!
  </button>
  <p>
  <!-- 現在時刻を表示させるプロパティを定義 -->
    {{ now }}
  </p>
</div>

js側でdataオプションにプロパティnowの設定と、methodsオプションにメソッドを定義する。

メソッドはメソッド名: function() {..処理..}で記述する。

this.nowでプロパティnowにアクセスできる。

var app = new Vue({
  el: '#app',
  data: {
    now: '' // nowに空の文字列を設定
  },
  methods: {
    onclick: function() {
      this.now = new Date().toLocaleString()
    }
  }
})

双方向データバインディング v-model

v-modelは双方向にデータバインディングする事ができる

これは実際に手を動かした方が分かりやすいかもしれない。

まずmessageプロパティを用意します。

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

後はv-model="プロパティ名"で双方向にデータバインディングされる。

具体的には、下記のinputの値を変更すると{{ message }}の値もリアクティブに書き換わる。

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

文字数カウントとかもめっちゃ楽。

  <p>
    <input type="text" v-model="message">
  </p>
  <p>
    文字数{{ message.length }}
  </p>

コンポーネント

よく使う機能を、再利用可能なコンポーネントにできる。

コードの見通しが良くなり、開発効率の改善に繋がる。

Vue.componentメソッドを使用する。

// Vueインスタンス作成よりも前に記述する
// Vue.component(コンポーネント名, { コンポーネントの定義情報 })`で作成
Vue.component('hello-component', {
  template: '<p>Hello</p>'
})

var app = new Vue({
  el: '#app',
  --以下省略--
  )}

コンポーネントの呼び出しはテンプレート名を記述するだけ。

<div id="app">
  <hello-component></hello-component> <!-- <p>Hello</p> -->
  <hello-component></hello-component> <!-- <p>Hello</p> -->
</div>

まとめ

Vue.js 楽しい:v:

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

Vue.jsの基本的な使い方まとめ

はじめに

jQueryガンギマリボーイから脱却するために学習を始めました。

目標はVue.jsとBaaSでオリジナルのポートフォリオを作る事。

「あの人絶対Vueキメてるよね」と言われるようになるまでレッツトライ

「Vueやりたいんだよねぇ〜」って人も僕と一緒にレッツトライ

HTML、CSS、JavaScriptの基礎知識がある程度必要になってきます。

---- Vue.js学習記録 ----
Vue.jsでTO DOアプリを作る update:zap:

Vueインスタンスの作成

elオプションでVueインスタンスを展開する場所を指定する。

#appid="app"を指定したことになる。

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

ルートテンプレートの作成

HTMLでVueインスタンスを展開する場所を指定する。

HTMLにVueを埋め込むイメージ。

<div id="app">
  <!--ここにVueインスタンスが展開される -->
</div>

テキストのデータバインディング

データバインディングとはJavaScriptのデータを変えると描画されている内容も同時に変わる仕組みのこと。

Vueインスタンスに設定した値はマスタッシュ構文で展開する。

マスタッシュ構文例
{{ message }}

Vueインスタンスのdataオプションmessageの値を設定する

var app = new Vue({
  el: '#app', // マウントするセレクタの指定
  data: {
    message: 'Hello Vue.js!'
  }
})

設定したmessageelオプションに定義したid=appの中で展開できる。

<div id="app">
  <p>
    {{ message }} <!-- Hello Vue.js! -->
  </p>
</div>

id=appの外では展開できない

<div id="app">
</div>
{{ message }} <!-- {{ message }} -->

dataオプションで定義したデータは外部からアクセスできる

console.log(app.message) // Hello Vue.js!

dataオプションにオブジェクトや配列を設定

カンマで続けて書く事ができる。

var app = new Vue({
  el: '#app',
  data: {
    // userオブジェクトを定義
    user: {
      lastName: 'Yamada',
      firstName: 'Taro',
      prefecture: 'Tokyo'
    },
    // 配列を定義
    colors: ['Red', 'Green', 'Blue']
  }
})

オブジェクトはオブジェクト名.キー名

配列は配列名[数値]でアクセス出来ます。

<div id="app">
  <p>
    {{ user.prefecture }} <!-- Tokyo -->
  </p>
  <p>
    {{ colors[0] }} <!-- Red -->
  </p>
</div>

属性のデータバインディング v-bind

属性のデータバインディングにはv-bindディレクティブを使用する。

<div id="app">
  <input type="text" v-bind:value="message" />
</div>

条件分岐 v-if v-show

v-ifの場合、要素はDOMレベル削除される。
頻繁に表示・非表示を繰り返す場合、描画コストが高くなる。

<div id="app">
  <p v-if="toggle"> <!-- 表示されない -->
    Hello
  </p>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    toggle: false
  }
})
</script>

v-showの場合、条件を満たさない時はdisplay: noneで非表示させる。

表示を頻繁に切り替える際はv-ifと比べて描画コストの点で有利になる。

<div id="app">
  <p v-show="toggle">
    Hello
  </p>
</div>

繰り返し処理 v-for

v-forディレクティブは繰り返し処理を行う事ができる。

試しにdataオプションに配列colorsを定義する。

var app = new Vue({
  el: '#app',
  data: {
    colors: ['Red', 'Green', 'Blue']
  }
})

v-forを使って配列の値を一つずつ取り出して繰り返し処理を行う。

配列名は複数形、繰り返し処理で使う値は単数形にしておくとコードが読みやすくなる。

<div id="app">
  <ol>
    <li v-for="color in colors">
    {{ color }
  </li>
  </ol>
</div>
結果
<div id="app">
  <ol>
    <li>Red</li>
    <li>Green</li>
    <li>Blue</li>
  </ol>
</div>

オブジェクトを繰り返し処理する

dataオプションにuserオブジェクトを定義する

var app = new Vue({
  el: '#app',
  data: {
    user: {
      firstName: 'Daiki',
      LastName: 'Tsuneta',
      age: 28
    }
  }
})

value in オブジェクト名でオブジェクトの中身を一つずつ取り出して繰り返し処理を行う

valueは任意の値を設定できる。vでも動く。

<div id="app">
  <ul>
    <li v-for="value in user">{{ value }}</li>
  </ul>
</div>
結果
<div id="app">
  <ul>
    <li>Daiki</li>
    <li>Tsuneta</li>
    <li>28</li>
  </ul>
</div>

オブジェクトのキーも一緒に表示させたい時はv-for="(値, キー) in オブジェクト名"で表示させる事ができる。

第一引数が値第二引数がキーになっているので注意。

<ul>
  <li v-for="(value, key) in user">
    {{ key }} : {{ value }}
  </li>
</ul>

イベント処理 v-on

v-onディレクティブはイベント処理の基本。

ボタンをクリックしたら現在時刻を表示させるプログラムを書いてみる。

<div id="app">
  <!-- クリックするとonclickメソッドが呼び出される -->
  <button v-on:click="onclick">
    Click!
  </button>
  <p>
  <!-- 現在時刻を表示させるプロパティを定義 -->
    {{ now }}
  </p>
</div>

js側でdataオプションにプロパティnowの設定と、methodsオプションにメソッドを定義する。

メソッドはメソッド名: function() {..処理..}で記述する。

this.nowでプロパティnowにアクセスできる。

var app = new Vue({
  el: '#app',
  data: {
    now: '' // nowに空の文字列を設定
  },
  methods: {
    onclick: function() {
      this.now = new Date().toLocaleString()
    }
  }
})

双方向データバインディング v-model

v-modelは双方向にデータバインディングする事ができる

これは実際に手を動かした方が分かりやすいかもしれない。

まずmessageプロパティを用意します。

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

後はv-model="プロパティ名"で双方向にデータバインディングされる。

具体的には、下記のinputの値を変更すると{{ message }}の値もリアクティブに書き換わる。

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

文字数カウントとかもめっちゃ楽。

  <p>
    <input type="text" v-model="message">
  </p>
  <p>
    文字数{{ message.length }}
  </p>

コンポーネント

よく使う機能を、再利用可能なコンポーネントにできる。

コードの見通しが良くなり、開発効率の改善に繋がる。

Vue.componentメソッドを使用する。

// Vueインスタンス作成よりも前に記述する
// Vue.component(コンポーネント名, { コンポーネントの定義情報 })`で作成
Vue.component('hello-component', {
  template: '<p>Hello</p>'
})

var app = new Vue({
  el: '#app',
  --以下省略--
  )}

コンポーネントの呼び出しはテンプレート名を記述するだけ。

<div id="app">
  <hello-component></hello-component> <!-- <p>Hello</p> -->
  <hello-component></hello-component> <!-- <p>Hello</p> -->
</div>

まとめ

Vue.js 楽しい:v:

次の記事:point_right: Vue.jsでTO DOアプリを作る

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

Vue.js+Vuetify.jsを使ったWEBアプリケーション構築 ~サンプルアプリケーションの構成~

はじめに

前回投稿した記事「Vue.js+Vuetify.jsを使ったWEBアプリケーション構築」で、Vueのサンプルアプリケーションを触ってみて頂いたと思いますので、今回はこのサンプルアプリケーションの構成について説明したいと思います。
今回の記事でサンプルアプリケーションを参考にすればVueで独自のアプリケーションを構築できそうというイメージを持って頂けたら幸いです。

アプリケーションの全体図

サンプルアプリケーションは、Vueの単一ファイルコンポーネントの機能を使って構築しています。
全体図としてコンポーネント図を描いてみました。
※アプリケーションの主要なコンポーネントだけ記載しています。
画面コンポーネント図.png

説明

上記の「アプリケーションの全体図」を基に説明します。
各コンポーネントに色がついていますが、それぞれの役割は以下の通りです。

コンポーネントの色 役割
SPA(シングルページアプリケーション)の根幹部分
各種外部ライブラリの設定
画面
黄色 画面部品(画面の構成要素)
グレー 基礎部品(共通的に利用できる部品)

緑のコンポーネント

緑のコンポーネントがSPAの根幹部分で「index.html」から「main.js」を読み込み「App.vue」で画面を作成します。シングルページなので、ブラウザからアクセスされるのは「index.html」のみです。
では、どのようにして画面を切り替えているのか?
Vue Routerの出番です。Vue Routerについては以下の「白のコンポーネント」で説明します。

白のコンポーネント

画面の切り替えを行うにはVue Routerを使ってURLと画面の紐付けを行います。サンプルアプリケーションでは「router.js」で行っています。
例)「/【表示言語】/ichiran」というURLは「Ichiran」というコンポーネントを表示

router.js
// ・・・
import Ichiran from './components/screen/Ichiran';
// ・・・

let routes = [
// ・・・
// PATH_PREFIXにはjaやenなどの表示言語が入る。
  {
    path: PATH_PREFIX + '/ichiran',
    name: 'ichiran',
    component: Ichiran
  },
//  ・・・
];

このように白のコンポーネントは外部ライブラリの設定など、アプリケーション全体の動作を定義するコンポーネントとなっています。

各コンポーネントと外部ライブラリの対応は以下の通りです。Vueを全く知らない人向けに外部ライブラリの超概要も記載しておきます。

コンポーネント 外部ライブラリ名 超概要
axiosObj.js Axios 非同期のHTTP通信
i18n.js Vue I18n 多言語対応
router.js Vue Router 画面遷移
store/index.js Vuex アプリケーション内のデータ共有
vee-validate.js VeeValidate 入力値検証
vuetify.js Vuetify マテリアルデザインを取り入れた単一ファイルコンポーネントやCSSなど

青、黄色、グレーのコンポーネント

青、黄色、グレーのコンポーネントはいずれもVueの単一ファイルコンポーネントで、画面を構成するコンポーネントです。コンポーネントの情報整理として、それぞれ画面(青)、画面部品(黄色)、基礎部品(グレー)というグループに分けました。
サンプルアプリケーションでは以下のルールを設けています。

  • 青のコンポーネントは画面として公開するため、Vue RouterでURLを割り当てます。
  • 青のコンポーネントは必ずBaseLayoutを利用します。
  • 黄色のコンポーネントは青のコンポーネントから参照されます。
  • グレーのコンポーネントはどのコンポーネントからも利用できるものとします。

サンプルアプリケーションの一覧画面を例に各コンポーネントの関係性を表すと以下のようになります。
画面レイアウト.png

また、各画面の実装でVuetifyを利用することで、独自のCSSはほとんど定義せずにリッチな画面を作成できることが、ソースコードを見て頂ければわかっていただけると思います。

おわりに

全体的な動作を変えなくて良いのであれば、サンプルアプリケーションをベースに青(画面)と黄色(画面部品)のコンポーネントを独自に作成するだけで、アプリケーションは容易に作成できると思います。(URLの紐づけは必要になりますが。)
サンプルアプリケーションは、ビルドの設定や単体テストの設定なども行っています。これからVueでアプリケーションの構築を考えている方の参考になれば幸いです。

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

@nuxtjs/vuetify で Vuetify を v2.0 にアップデートする

@nuxtjs/vuetify を使用して導入している Vuetify のバージョンアップを行ったのでメモ

バージョン

0.5.52.0.0-beta.0 に。
fibar が私の環境ではインストールできなかったため、
fibar が optionalDependencies になる @nuxtjs/vuetify@2.0.0-beta.0 を使用。

@nuxtjs/vuetify の変更点

v2.0@alpha - Module revamp

frameworkOptions

Vuetify 側の設定と @nuxtjs/vuetify 側の設定が分けられたようです。
@nuxtjs/vuetify の設定を frameworkOptions に移動する必要がありました。

nuxt.config.js
module.exports = {
  vuetify: {
    frameworkOptions: {
      icons: {
        iconfont: 'md',
        values: {}
      }
    },
    customVariables: ['path/to/customize.scss']
  },
}

defaultAssets

0.5.5 では materialIcons という Material Icons を自動で導入してくれるプロパティがありましたが、
defaultAssets というプロパティに置き換わりました。
諸事情あってCDNではなくローカルサーバーに置いたアイコンを使いたかったのでOFFに。

module.exports = {
  modules: [
-    ['@nuxtjs/vuetify', { materialIcons: false }]
+    ['@nuxtjs/vuetify', { defaultAssets: false }]
  ]
}

Vuetify v2.0 の変更点

ここからは Vuetify 側の変更点で引っかかった点を記述

コンポーネントや props の名前・型変更

変更点は マイグレーションガイド にまとまっていますが、大部分のコンポーネントに命名変更がかかっています。

特に data-tables は変更点が多く、名前だけでなく構造も変わっているものがあります。
options.sortBy といったソートの設定が、複数カラムのソートに対応した影響か配列しか受け付けなくなっていたのは若干詰まりました。

// data-tables の options
{
  page: number
  itemsPerPage: number
  sortBy: string[]
  sortDesc: boolean[]
  groupBy: string[]
  groupDesc: boolean[]
  multiSort: boolean
  mustSort: boolean
}

data-tables の headerCell

v1.5 の data-tables に存在した headerCell スロットにあたる機能が v2.0 では無くなっているようです。
代わりに header.<name> で各ヘッダーを個別にカスタマイズできるようになっています。

私のプロジェクトではこの name が可変のテーブルを作っていたので header.<name>
あらかじめ設定しておくことができず、v-slot:header でヘッダー丸ごと上書きする必要がありました。

<template v-slot:header="{ props: { headers, options }, on: { sort } }">
  <thead>
    <tr>
      <th
        v-for="(header, index) in headers"
        :key="index"
        :class="['column sortable',
                 options.sortDesc[0] ? 'desc' : 'asc',
                 header.value === options.sortBy[0] ? 'active' : '',
                 header.align == null ? 'text-left' : '',
                 header.align === 'center' ? 'text-center' : '',
                 header.align === 'end' ? 'text-right' : '']"
        @click="sort(header.text)"
      >
        <!-- 中身 -->
      </th>
    </tr>
  </thead>
</template>

こんな感じでクラスやクリックイベントを元と近い挙動になるように設定。
全くスマートではないが、他に良いやり方が見つからず。

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

Vuexを初めから丁寧に(1)~Vuexを理解するために必須の前提知識~

はじめに

この記事を読むと

  • Vuexを理解するために必要な知識を習得できます
  • Vuexを学ぶためのマイルストーンが明確となります

想定読者

  • Vue.js や Nuxt.js の初級〜中級者
  • Vuex を何となく雰囲気で使っている

前提知識

JavaScript 及び Vue についての基本知識があることは前提とします。
(Vue の基本知識がない方はこちらが入門書として最も最適です。)
『Vue.js 超入門』(掌田津耶乃/秀和システム)

またJavaScriptにおいては特に、オブジェクトの使い方にも慣れておくとスムーズでしょう。
(こちらの第9章が最も良い説明だと思います。)
『初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発』(Ethan Brown, 武舎広幸,武舎るみ/オライリージャパン)

Vuex の理解が難しい原因

なぜ Vuex が難しいと感じるのでしょうか?
私の場合は専門用語の意味が省略されていることに起因していました。
さらに問題なのは、 「Vuexを理解するためのキーとなる用語」が、全く違う意味で使われているにも関わらず見た目は一般的な日本語と一緒なのでなんとなくわかった気になり、「何が分からないのか分からない」状況に陥ることです。
例えば Vuex における「状態」
「アプリケーションが保持するデータ」
のことを指します。
なので、「Vuex は状態管理ライブラリである」「Vuex は状態を管理するために単方向データフローを採用している」といった説明や図解※を見ても、肝心の「状態」が分からないので、文章の意味が消化できないまま頭を素通りしていくだけでした。

しかし逆に言うと、用語の意味さえ押さえておけば Vuex はスラスラ理解できます。

※Vuexデータフローの図解
Vuex図解(Vuex公式ドキュメントより)

Vuex を理解するためのツボ

さて、前置きが長くなりましたが本題です。
たった 4 つだけです。

  • 用語を正確に理解する

    • 「状態」
    • 「データフロー」
  • 「データフローの設計」と「状態管理」の意義を理解する

    • 信頼できる唯一の情報源(Single Source of Truth)
    • 単方向フロー(one-way data flow)
    • 情報と取得のカプセル化(Encapsulation of sorce and receiving)
  • Vuex の構成要素の役割と使い方を理解する

    • State
    • Getters
    • Mutations
    • Actions
  • ※「ストアのモジュール分割」は一旦省略します

Vuex に入る前に

いきなり Vuex に入るより、まず状態管理やデータフローの基本知識を押さえておくと、スムーズに理解が進みます。

「状態」とは

状態とは
「アプリケーションが保持するデータ」
のことです。
ユーザーの操作やイベントの発生などによってその値が更新されていきます。

例えば、EC サイトのショッピングカートです。カートは何も入っていない空の状態から始まり、ユーザーが商品をカートに入れる操作を行うことでカートは空の状態に戻り、購入処理が完了します。

規模が大きいアプリケーションは保持する状態の数、それぞれの組み合わせの数も多くなり、そのままでは扱いきれなくなります。

繰り返しになりますが、Vuex において「状態」は普段の日本語とは異なる特別な意味がある言葉なので注意してください。

データフローとは

「データフロー」とは
「状態を含む、アプリケーションが持つデータの流れ」
のことを指します。
具体的には、どこにデータを保持し、データを読み込む時や更新するときはどこからどのように行うのかという点を表すことが多いです。

データフローの設計において、以下の三つのプラクティスが重要です。

信頼できる唯一の情報源

「信頼できる唯一の情報源」(single source)とは、「管理する対象のデータを一箇所に集約することで管理を容易にすることを目的とする設計のパターン」です。

  • どのコンポーネントも同一のデータを参照するため、データや表示の不整合が発生しづらい
  • 複数のデータを組み合わせた処理を比較できる容易に実装できる
  • データの変更のログ出力、現在のデータの確認などの開発に便利なツールを作りやすい

「状態の取得・更新」のカプセル化

「状態の取得・更新」のカプセル化を行うことで、状態管理のコストを下げることができます。
例えばカウンターアプリの例では更新処理を store 内に記述することでカプセル化しており、コンポーネント側からは具体的にどのような実装がされているかは隠されています。

  • 状態の取得・更新のロジックを様々な場所から利用できる
  • 詳細な実装をビューから隠すことで、データ構造や取得、更新処理の変更の影響範囲を小さくする
  • デバッグ時に確認する場所が限られるため、デバッグが容易になる

単方向データフロー

単方向データフローにすることで、状態の取得、更新のコードが簡潔になります。
データが単方向でないと、データの取得と更新の両方を同時にできてしまい、より複雑な処理になり理解が難しくなってしまいます。

  • データを取得しつつ更新するといったようなことができなくなり、実装やデバッグが単純になる
  • データを取得、更新するために何をするかの選択肢が絞られて、理解が容易なコードをかきやすい

まとめ

ここまでデータフローの三つのプラクティスを見てきましたが、実はVuex は先ほど紹介したデータフローのプラクティスを全て満たします。

まず、Vuex はアプリケーションの状態やそれに付随するロジックが一つの場所(ストア)にまとまるように設計されているため、「信頼できる唯一の情報源」を満たします。

また、Vuex において状態の更新はミューテーションでのみ行うことができ、取得に関してもゲッターという機能で詳細な実装は隠蔽できるため「状態の取得と更新」のカプセル化も満たします。

さらに、状態の取得と更新の窓口が異なるため(冒頭の図解をもう一度参照ください)、強制的に実装が単方向データフローになります。

おわりに

いかがだったでしょうか。VueやNuxtで開発を行う方が、Vuexを理解するための助けになれば幸いです。
「状態管理」「データフロー」についてはバッチリですか?
次の記事ではいよいよ Vuex による状態管理について見ていきます。

参考文献

『Vue.js入門 基礎から実践アプリケーション開発まで』(川口和也, 喜多啓介, 野田陽平, 手島拓也, 片山真也/技術評論社)
Vue.jsについての書籍は増えてきていますが、問題なのはその殆どがVuexについての説明を省略していることです。Vue.jsやNuxt.jsを用いた実際の開発においてVuexによる状態管理は必須ですが、学習の障壁になるとして避けてしまっているのでしょう。私が読んだ中で唯一、Vuexについて丁寧に説明していたのが本書です。Vuex以外の内容も素晴らしいの一言。本書はVue.js・Nuxt.jsの開発に関わるエンジニアや組織にとって必携です。保存用・実用用・観賞用に3冊購入しましょう。あるいは、あなたが経営者の場合はぜひエンジニアに対して一人一冊ずつ買い与えてください。
ただし、全くVueについて未経験という方への第一歩としては内容が本格的すぎるかもしれません。その場合は『Vue.js 超入門』がおすすめです。

『Vue.js 超入門』(掌田津耶乃/秀和システム)
とにかく分かりやすく、まず概要を把握するために最適の一冊です。「なんとなくで良いので概要を把握する」⇨「より詳細で厳密な理解する」という流れで学ぶとスムーズです。

『初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発』(Ethan Brown, 武舎広幸,武舎るみ/オライリージャパン)
JavaScriptの根本的な理解ができる、革命的な良書です。分厚いので手強そうに見えますが、実際はとても親切で分かりやすい作りです。本書も一人一冊は欲しいところです。

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

Nuxt.js + apollo-module(vue-apollo) + TypeScriptで型をつける方法

Vue.jsでGraphQLを使う際は、Vue Apolloが選択肢の一つになると思います。

GraphQLのクライアントはfetchやXMLHttpRequestを使用しても実装できます。しかし、Nuxt.jsでSSRすることを考えると、サーバサイドでも取得できるようにしたり、Vueのdataとマージしたりと、色々と考慮すべきことや実装すべきポイントがあります。Vue Apolloを使うと、こういった部分であまり悩まなくて済みます。

※ただし、Apolloは高機能である分、使いこなすまで時間がかかりますし、ユースケースによってはオーバースペックな可能性もあります。

Vue ApolloはVue.jsのプラグインで、ApolloのGraphQLクライアントをVue.jsに統合することができます。

さらに、Nuxt.jsのapollo-moduleは、Vue ApolloをNuxt.jsに統合しています。

この記事では、Nuxt.js + TypeScript + apollo-moduleで開発する際に、型をつける方法を解説します。

なお、この記事で使っているソフトウェアのバージョンは下記の通りです。

  • nuxt: 2.12.0
    • vue: 2.6.11
  • typescript: 3.8.3
  • nuxtjs/apollo: 4.0.0-rc19
    • vue-apollo: 3.0.3

また、本記事のサンプルコードは下記リポジトリでも公開しています。

https://github.com/ryo-utsunomiya/nuxt-apollo-typescript

セットアップ

下記コマンドでNuxtプロジェクトを作成します。

npx create-nuxt-app nuxt-apollo-typescript

セットアップ中の設定は、プログラミング言語がTypeScript、レンダリングモードがUniversal (SSR)になっていれば、他は何でもよいです。

fetchによるGraphQLクライアント

まずはシンプルなところから始めたいので、fetchを使った最小限のGraphQLクライアントを作ってみます。GraphQL APIはサンプルとしてよく使われるSW APIを使います。

pages/index.vue
<template>
  <div :class="$style.container">
    <h1>Star wars films</h1>
    <ul>
      <li v-for="film in films" :key="film.episodeID">
        {{ film.title }}
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

// GraphQLのレスポンスに型をつける
interface Film {
  episodeID: number
  title: string
}
interface Edge {
  node: Film
}
interface FilmConnection {
  edges: Edge[]
}
interface ResponseData {
  allFilms: FilmConnection
}
interface Response {
  data: ResponseData
}

// Vueのdataに型をつける
interface VueData {
  films: Film[]
}

export default Vue.extend({
  data(): VueData {
    return {
      films: []
    }
  },
  async created() {
    const query = `
{
  allFilms(first: 3) {
    edges {
      node {
        episodeID
        title
      }
    }
  }
}
`
    const res = await fetch(
      'https://swapi-graphql.netlify.com/.netlify/functions/index',
      {
        method: 'POST',
        body: JSON.stringify({
          query
        }),
        headers: {
          'Content-Type': 'application/json'
        }
      }
    ).then<Response>((res) => res.json())

    this.films = res.data.allFilms.edges.map((e) => e.node)
  }
})
</script>

<style module>
.container {
  margin: 10px;
}
</style>

上手くいったら、↓のようにタイトルのリストが表示されます。

Screen Shot 2020-03-25 at 1.45.08.png

ただし、このコードには残念な点もあります。 fetch はブラウザのAPIなので、SSR時にデータを取得しようとするとエラーになります( created => asyncData に変更すると、SSR時のエラーを再現できます)。

この問題の解決策としては node-fetch を使うといった方針もありですが、ここではNuxtのapollo-moduleを使って解決してみます。

apollo-moduleのインストール

まず、公式のインストールガイドに沿って、apollo-moduleをインストールします。

yarn add @nuxtjs/apollo graphql-tag

nuxtの設定ファイルにモジュールを読み込み、最低限の設定を記述します。

nuxt.config.js
  modules: ['@nuxtjs/apollo'],
  apollo: {
    clientConfigs: {
      default: {
        httpEndpoint:
          'https://swapi-graphql.netlify.com/.netlify/functions/index'
      }
    }
  },

この状態で、pages/index.vueのscriptを以下のように書き換えると、Apolloを使ってデータが取得できるようになります!

pages/indev.vue
<script>
import gql from 'graphql-tag'

export default {
  computed: {
    films() {
      return this.allFilms.edges.map((e) => e.node)
    }
  },
  apollo: {
    allFilms: gql`
      query {
        allFilms(first: 3) {
          edges {
            node {
              episodeID
              title
            }
          }
        }
      }
    `
  }
}
</script>

Vueコンポーネントの定義にapolloというプロパティを生やして、ここにGraphQL APIに対するクエリを書くと、レスポンスがdataにマージされます。

TypeScriptの型定義

現状では、apolloによるデータ取得はできましたが、TypeScriptの型は付いていません。そこで、 lang="ts" にして、 Vue.extend() を使って型をつけようとすると、コンパイルエラーが発生します。

pages/index.vue
<script lang="ts">
import Vue from 'vue'
import gql from 'graphql-tag'

export default Vue.extend({
  computed: {
    films() {
      return this.allFilms.edges.map((e) => e.node)
    }
  },
  apollo: {
    allFilms: gql`
      query {
        allFilms(first: 3) {
          edges {
            node {
              episodeID
              title
            }
          }
        }
      }
    `
  }
})
</script>

コンパイルエラー:

No overload matches this call.
  The last overload gave the following error.
    Argument of type '{ computed: { films(): any; }; apollo: { allFilms: DocumentNode; }; }' is not assignable to parameter of type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>'.
      Object literal may only specify known properties, and 'apollo' does not exist in type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>'.

これは、VueのOptions APIにapolloというフィールドが存在しないために発生しています。そこで、tsconfig.jsonを修正し、VueのOptions APIでapolloが定義できるよう修正しましょう。

  "compilerOptions": {
    "types": [
      "@types/node",
      "@nuxt/types",
+     "vue-apollo"
    ]
  }

これで、TypeScriptのコンパイル時にVue Apolloが定義しているapolloの型定義が読み込まれるため、apolloの型がつくようになります。

あとは this.allFilms の型を直せば、コンパイルが通るようになります。

pages/index.vue
<script lang="ts">
import Vue from 'vue'
import gql from 'graphql-tag'

// GraphQLのレスポンスに型をつける
interface Film {
  episodeID: number
  title: string
}
interface Edge {
  node: Film
}
interface FilmConnection {
  edges: Edge[]
}

// Vueのdataに型をつける
interface Data {
  allFilms: FilmConnection
}

export default Vue.extend({
  data(): Data {
    return {
      allFilms: {
        edges: []
      }
    }
  },
  computed: {
    films(): Film[] {
      return this.allFilms.edges.map((e) => e.node)
    }
  },
...

レスポンスの型をスキーマから生成する

現状では、 FilmConnection のような型を手書きしていますが、こういった型はGraphQLのスキーマから自動生成したいところ。そこで、まずは以下のURLからスキーマをもってきます。

https://github.com/graphql/swapi-graphql/blob/master/schema.graphql

これを schema.graphql といった名前でプロジェクト内に置いておきます。次に、GraphQL Code Generatorを使って、このスキーマからTypeScriptの型を生成します。

まずはライブラリのインストール

yarn add -D @graphql-codegen/cli @graphql-codegen/typescript

次に設定ファイル。

codegen.yml
overwrite: true
schema: "schema.graphql"
generates:
  lib/GraphQL/generated.ts:
    plugins:
      - "typescript"

準備ができたらコマンドを叩きます。

yarn run graphql-codegen --config codegen.yml

上手くいけば、以下のようなファイルがができているはず。

lib/GraphQL/generated.ts
export type Maybe<T> = T | null;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
};
...

あとは、これをimportして使うだけです。先ほどと少し型定義が変わってるので、それに合わせてコードも直してます。

pages/index.vue
<script lang="ts">
import Vue from 'vue'
import gql from 'graphql-tag'
import { FilmsConnection, Film } from '~/lib/GraphQL/generated'

interface Data {
  allFilms?: FilmsConnection
}

export default Vue.extend({
  data(): Data {
    return {
      allFilms: undefined
    }
  },
  computed: {
    films(): Film[] {
      if (this.allFilms == null || this.allFilms.edges == null) return []
      return this.allFilms.edges
        .map((e) => e?.node)
        .filter((f): f is Film => f != null)
    }
  },
  apollo: {
    allFilms: gql`
      query {
        allFilms(first: 3) {
          edges {
            node {
              episodeID
              title
            }
          }
        }
      }
    `
  }
})
</script>

これによって、 lang="ts" なVueコンポーネントでapolloが使えるようになり、さらに、GraphQLのレスポンスに型がつけられるようになりました。

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

JavaScript系:Qiitaのタグ統計を浄化するためのタグ詰め合わせ記事

タグだけ

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

Qiita記事のエクスポート

経緯

ぼく

前々からQiitaあんまり好きじゃなかったんで全く使ってなかったんですがマジでキツいのでエクスポートします.

jqhttpieを使ってるのでMac使いの人は次のコマンドでよしなにしてください

それ以外のユーザは知りません

Install Dependencies

$ brew install jq httpie

Export Posts

http --follow 'qiita.com/api/v2/users/ユーザID/items' | jq '.[] | .url' | xargs -I{} echo {} | sed 's/$/\.md/g' | xargs wget

ユーザ認証のないAPIなので公開してる記事しか取れません

お わ り

Qiitaみんなやめようねという話ではないけど少なくとも僕はやめます

追記

あまりに腹が立つのでそれっぽいサイトを作ってみました

goodbye-qiita.netlify.com

Screen Shot 2020-03-26 at 4.05.23.png

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

vuetify環境構築とやたら出てくるv-slotについておさらい

前回記事でvueとexpressでフロントエンドとバックエンドを繋ぐことができるようになった。
次はフロントエンド側を綺麗に見せるためにvuetifyというUIフレームワークをいれてみる。

vuetifyをインストール

※ 実行すると、App.vueやmain.ts、package.jsonなど複数のファイルがvuetifyのデフォルトに上書きされる。実行前にどっかに退避させておくのが大事。

package.jsonを含むディレクトリ($vue create frontでプロジェクトfrontを作成していればfront下にある)で以下のコマンド実行。

$ vue add vuetify
?  Installing vue-cli-plugin-vuetify...

+ vue-cli-plugin-vuetify@2.0.5
added 4 packages from 4 contributors and audited 40952 packages in 8.254s
found 25 vulnerabilities (20 low, 5 moderate)
  run `npm audit fix` to fix them, or `npm audit` for details
✔  Successfully installed plugin: vue-cli-plugin-vuetify

? Choose a preset: Default (recommended)

?  Invoking generator for vue-cli-plugin-vuetify...
?  Installing additional dependencies...

added 2 packages from 1 contributor and audited 40959 packages in 11.674s
found 25 vulnerabilities (20 low, 5 moderate)
  run `npm audit fix` to fix them, or `npm audit` for details
⚓  Running completion hooks...

✔  Successfully invoked generator for plugin: vue-cli-plugin-vuetify
   The following files have been updated / added:

     src/assets/logo.svg
     src/plugins/vuetify.ts
     vue.config.js
     package-lock.json
     package.json
     public/index.html
     src/App.vue
     src/components/HelloWorld.vue
     src/main.ts

   You should review these changes with git diff and commit them.

 vuetify  Discord community: https://community.vuetifyjs.com
 vuetify  Github: https://github.com/vuetifyjs/vuetify
 vuetify  Support Vuetify: https://github.com/sponsors/johnleider

$ npm run serve

新しくなったvuetifyのページが現れる。App.vueは消え去っている。。

まずは、エラーを消す作業から。

参考としたのはこの2つ

2020年3月現在ではtsconfig.jsonの修正のみでエラーはなくなった。

  "types": [
      "webpack-env",
      "mocha",
      "chai",
      "vuetify"  //これを追記。
  ]

その後失われたApp.vueを元に戻す。

なお、無事インストールできたのにこんな感じのエラーが出てフロントエンドが立ち上がらない場合はサーバーを再起動してもう一回npm run serveすると治る。

throw er; // Unhandled 'error' event

参考) https://qiita.com/M-ISO/items/d693ac892549fc95c14c

基本的な使い方

App.vueなどを参考にするとわかるが、vuetifyでは必ず<v-app></v-app>で囲われた中で各種タグを使う必要がある。

<template>
    <v-app>
        ここでv-〇〇達を使ってあげる。
    </v-app>
</template>

v-slot

vuetifyを見ているとv-slotを使うものがいくつか見つかる。
正直使わないから飛ばしててまともに知らなかったので勉強。

これまでコンポーネントの親子の関係では親から子に変数やjsonなどの値をプロパティとして渡し、値をどう表示するかは子コンポーネント側で指定していた。ボタンに表示する文言を親から受け取るけれど、どこに配置するかなどは子側で決めるといった具合だろうか。
slotは、子コンポーネントの中身を直接ハードコードせず、親側で動的に決められるようにした仕組み。これにより、vuetifyなどのUIフレームワークに対して柔軟に値をわたし、表現することができるようになっている。

スロットでは、子コンポーネントの中で<slot></slot>とした部分に対し、親側から指定されたテンプレートがまるごとはめ込まれる。
slotタグの間にはデフォルトの表示を指定しておくこともできる。

名前付きスロット

また、以下のように複数のスロットを名前で区別することもできる。(以下公式リファレンスの例)

子base-layoutコンポーネント
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>   *
  <p>And another one.</p>                    *

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>
  • 子コンポーネントで名前を指定しないもの(main)はdefaultという名前が暗黙裡につけられる。
  • 親側で名前を指定しない箇所(*)はdefaultに入る。<template v-slot:default>とすることも可能。

スコープ付きスロット

子のプロパティに対してslotで流し込むテンプレートの中からアクセスしようとするとundefinedとなってしまい、アクセスできない。
これを回避するためにスロットに対してスコープを当てる。

子current-userコンポーネント
<span>
  <slot v-bind:user="user">  
    {{ user.lastName }}
  </slot>
</span>
<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

//もしくは省略形

<current-user>
  <template v-slot:default="{ user }">
    {{ user.firstName }}
  </template>
</current-user>

//もっと省略形

<current-user>
  <template #default="{ user }">
    {{ user.firstName }}
  </template>
</current-user>

上記は親が子のプロパティをslotPropsとして受け取り、そのなかから必要なuserの中のfirstNameにアクセスして表示するという流れ。slotPropsは特になんでもいいしなんなら省略形で書くことで省ける。

一例 v-menu

実際にv-slotが使われる1つの例としてv-menuがある。
以下の例ではアイコンv-iconを格納したボタンv-btnをスロットに渡している。クリック時にコンポーネントが有効になる。

<v-menu
    left
    bottom
>
    <template v-slot:activator="{ on }">
    <v-btn icon v-on="on">
        <v-icon small>mdi-arrow-down-drop-circle</v-icon>
    </v-btn>
    </template>

    <v-list>
        <v-list-item
            v-for="n in 5"
            :key="n"
            @click="() => {}"
        >
            <v-list-item-title>Option {{ n }}</v-list-item-title>
        </v-list-item>
    </v-list>
</v-menu>

v-menuはdefaultとactivatorの2種類のスロットを持ち、ここで登場しているactivatorは以下のプロパティを持っている。

{
  on: { [eventName]: eventHandler }
  value: boolean
}

このonを受け取り変数onに格納するのがv-slot:activator="{ on }"
ここで受けるonは複数のイベントハンドラを一括で指定できるオブジェクト構文という形で指定している。

詳しくは以下参照
参考)https://qiita.com/aotoriii/items/06ae49c135061a12b75e

トラブルシュート

<v-icon>で括った文字列がそのまま表示されてしまう場合

おそらくこれで対応可能(vuetifyのversion2.x云々の問題?)
https://qiita.com/tamonmon/items/5656614462241cd00255

以下のパッケージを入れる。

front$ npm install @mdi/js file-loader material-design-icons-iconfont --save-dev

以下ファイルに追記。

front/src/plugins/vuetify.ts
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
import 'material-design-icons-iconfont/dist/material-design-icons.css';


Vue.use(Vuetify);

export default new Vuetify({
});

上記対応後再度実行すると正常にアイコンが表示される。

98% after emitting CopyPlugin

で止まってしまったら一度サーバを再起動してもう一回実行するとうまくいく。

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