- 投稿日:2020-03-26T19:32:18+09:00
【Vue.js】Vue.jsの基本的な使い方まとめ【入門】
はじめに
jQueryガンギマリボーイから脱却するために学習を始めました。
目標はVue.jsとBaaSでオリジナルのポートフォリオを作る事。
「あの人絶対Vueキメてるよね」と言われるようになるまでレッツトライ
「Vueやりたいんだよねぇ〜」って人も僕と一緒にレッツトライ
HTML、CSS、JavaScriptの基礎知識がある程度必要になってきます。
Vueインスタンスの作成
elオプション
でVueインスタンスを展開する場所を指定する。
#app
はid="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!' } })設定した
message
はelオプション
に定義した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 楽しい
- 投稿日:2020-03-26T19:32:18+09:00
Vue.jsの基本的な使い方まとめ
はじめに
jQueryガンギマリボーイから脱却するために学習を始めました。
目標はVue.jsとBaaSでオリジナルのポートフォリオを作る事。
「あの人絶対Vueキメてるよね」と言われるようになるまでレッツトライ
「Vueやりたいんだよねぇ〜」って人も僕と一緒にレッツトライ
HTML、CSS、JavaScriptの基礎知識がある程度必要になってきます。
---- Vue.js学習記録 ----
Vue.jsでTO DOアプリを作る updateVueインスタンスの作成
elオプション
でVueインスタンスを展開する場所を指定する。
#app
はid="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!' } })設定した
message
はelオプション
に定義した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 楽しい
次の記事 Vue.jsでTO DOアプリを作る
- 投稿日:2020-03-26T19:18:07+09:00
Vue.js+Vuetify.jsを使ったWEBアプリケーション構築 ~サンプルアプリケーションの構成~
はじめに
前回投稿した記事「Vue.js+Vuetify.jsを使ったWEBアプリケーション構築」で、Vueのサンプルアプリケーションを触ってみて頂いたと思いますので、今回はこのサンプルアプリケーションの構成について説明したいと思います。
今回の記事でサンプルアプリケーションを参考にすればVueで独自のアプリケーションを構築できそうというイメージを持って頂けたら幸いです。アプリケーションの全体図
サンプルアプリケーションは、Vueの単一ファイルコンポーネントの機能を使って構築しています。
全体図としてコンポーネント図を描いてみました。
※アプリケーションの主要なコンポーネントだけ記載しています。
説明
上記の「アプリケーションの全体図」を基に説明します。
各コンポーネントに色がついていますが、それぞれの役割は以下の通りです。
コンポーネントの色 役割 緑 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を利用します。
- 黄色のコンポーネントは青のコンポーネントから参照されます。
- グレーのコンポーネントはどのコンポーネントからも利用できるものとします。
サンプルアプリケーションの一覧画面を例に各コンポーネントの関係性を表すと以下のようになります。
また、各画面の実装でVuetifyを利用することで、独自のCSSはほとんど定義せずにリッチな画面を作成できることが、ソースコードを見て頂ければわかっていただけると思います。
おわりに
全体的な動作を変えなくて良いのであれば、サンプルアプリケーションをベースに青(画面)と黄色(画面部品)のコンポーネントを独自に作成するだけで、アプリケーションは容易に作成できると思います。(URLの紐づけは必要になりますが。)
サンプルアプリケーションは、ビルドの設定や単体テストの設定なども行っています。これからVueでアプリケーションの構築を考えている方の参考になれば幸いです。
- 投稿日:2020-03-26T16:45:32+09:00
@nuxtjs/vuetify で Vuetify を v2.0 にアップデートする
@nuxtjs/vuetify を使用して導入している Vuetify のバージョンアップを行ったのでメモ
バージョン
0.5.5
→2.0.0-beta.0
に。
fibar が私の環境ではインストールできなかったため、
fibar が optionalDependencies になる@nuxtjs/vuetify@2.0.0-beta.0
を使用。@nuxtjs/vuetify の変更点
frameworkOptions
Vuetify 側の設定と @nuxtjs/vuetify 側の設定が分けられたようです。
@nuxtjs/vuetify の設定をframeworkOptions
に移動する必要がありました。nuxt.config.jsmodule.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>こんな感じでクラスやクリックイベントを元と近い挙動になるように設定。
全くスマートではないが、他に良いやり方が見つからず。
- 投稿日:2020-03-26T13:40:04+09:00
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 を理解するためのツボ
さて、前置きが長くなりましたが本題です。
たった 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の根本的な理解ができる、革命的な良書です。分厚いので手強そうに見えますが、実際はとても親切で分かりやすい作りです。本書も一人一冊は欲しいところです。
- 投稿日:2020-03-26T13:02:55+09:00
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>上手くいったら、↓のようにタイトルのリストが表示されます。
ただし、このコードには残念な点もあります。
fetch
はブラウザのAPIなので、SSR時にデータを取得しようとするとエラーになります(created
=>asyncData
に変更すると、SSR時のエラーを再現できます)。この問題の解決策としては node-fetch を使うといった方針もありですが、ここではNuxtのapollo-moduleを使って解決してみます。
apollo-moduleのインストール
まず、公式のインストールガイドに沿って、apollo-moduleをインストールします。
yarn add @nuxtjs/apollo graphql-tagnuxtの設定ファイルにモジュールを読み込み、最低限の設定を記述します。
nuxt.config.jsmodules: ['@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.ymloverwrite: true schema: "schema.graphql" generates: lib/GraphQL/generated.ts: plugins: - "typescript"準備ができたらコマンドを叩きます。
yarn run graphql-codegen --config codegen.yml
上手くいけば、以下のようなファイルがができているはず。
lib/GraphQL/generated.tsexport 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のレスポンスに型がつけられるようになりました。
- 投稿日:2020-03-26T02:21:11+09:00
Qiita記事のエクスポート
経緯
Qiita、ユーザーが読んだ記事の傾向を勝手に晒してくるんだ。ふーーーーん。 https://t.co/I3TL1HdbIX pic.twitter.com/xpUJgazdAp
— tadsan (@tadsan) March 25, 2020Qiita、ユーザーが読んだ記事を解析して結果を合意もとらずに公開する時点で相当アレだと思うけど、「公開したくなければオプトアウト」と提示してるリンクが別会社のサイトで、そこを見てもオプトアウトの方法が皆目分からないのはもうサービスとして使ってはいけない領域になったと思う。 pic.twitter.com/jhq5aF2OiY
— Keiji ARIYAMA (@keiji_ariyama) March 25, 2020ぼく
前々からQiitaあんまり好きじゃなかったんで全く使ってなかったんですがマジでキツいのでエクスポートします.
jqとhttpieを使ってるのでMac使いの人は次のコマンドでよしなにしてください
それ以外のユーザは知りません
Install Dependencies
$ brew install jq httpieExport Posts
http --follow 'qiita.com/api/v2/users/ユーザID/items' | jq '.[] | .url' | xargs -I{} echo {} | sed 's/$/\.md/g' | xargs wgetユーザ認証のないAPIなので公開してる記事しか取れません
お わ り
Qiitaみんなやめようねという話ではないけど少なくとも僕はやめます
追記
あまりに腹が立つのでそれっぽいサイトを作ってみました
- 投稿日:2020-03-26T00:29:24+09:00
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つ
- https://dev.classmethod.jp/client-side/spa/getting-started-class-style-vuetify/
- https://github.com/vuetifyjs/vuetify/issues/8289
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.tsimport 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
で止まってしまったら一度サーバを再起動してもう一回実行するとうまくいく。