Vue.js について少し勉強したので、思い出したいところだけメモ
Vue.js の基本
基本ルートのテンプレート
1
2
3
4
5
|
<body>
<div id="app">
<!-- ここに書く -->
</div>
</body>
|
1
2
3
4
|
var app = new Vue({
el: '#app',
...
})
|
app
はセレクタ(#app
)に対応。
アプリと配置する要素を紐付けることをマウントという。
var app
に代入してもしなくてもよい。
ディレクティブ
ディレクティブ
ディレクティブとは、 DOM 要素に対して何かを実行することをライブラリに伝達する、マークアップ中の特別なトークンです。
Vue.js でのディレクティブのコンセプトは徹底的にシンプルです。Vue.js のディレクティブは、下記のフォーマットのように、接頭辞のついた HTML 属性の形でのみ表されます。
基本は以下。
ディレクティブ
:引数
.修飾子
= 値
(ex) v-bind:value.sync = "message"
Text(v-text="式"
, {{式}}
)
1
2
3
4
5
6
7
8
9
10
|
<!-- dataオプションにmessageプロパティが登録されているとして -->
<!-- messageの値を表示 -->
<h1 v-text="message"></h1>
<!-- こう書く場合のほうが多そう -->
<h1>{{message}}</h1>
<!-- 42が表示される -->
<h1>{{40+2}}</h1>
|
繰り返し(v-for="ループっぽい式"
)
1
2
3
4
5
6
7
|
<!-- dataオプションにitemsプロパティがリストとして登録されているとして -->
<li v-for="i in items">{{i}}</li>
<li v-for="(item, index) in items">{{i}}</li>
<!-- 要素ごとに条件でclass設定できたりする -->
<li v-for="item in array" v-bind:class="{rainbow_badge: lv <= 50}" v-if="item != 'a'">
|
key
としてユニークな値をとるプロパティを指定することで、要素の識別と効率的な描画処理が可能になる。key
がないと、リストが更新された際に、リスト内の全要素を再描画してしまう。(変化した要素をvueが特定できないため。)
1
|
<li v-for="item in array" :key="item.id">
|
条件
式を評価した結果に応じて要素を消したりする。値の真偽値解釈はJavaScriptの条件式と同じ。
v-if="式"
, v-show="式"
1
2
3
4
5
6
7
8
|
<!-- dataオプションにshowプロパティが登録されているとして -->
<!-- DOMごと消える -->
<div v-if="show">v-if {{show}}</div>
<template v-if="show"></template>
<!-- DOMには残るが、style="display: none;" 属性が付与されるため、消えたように見える -->
<div v-show="show">v-show {{show}} </div>
|
v-if
はDOMごと消えてるため、配下の要素で参照するプロパティの有無を条件に入れる等でエラー回避したりできる。また、監視が解除されて破棄されるため、次に描画されるときは状態が初期化されてしまう。v-if
は単一な要素にしか付与で機内が、template
要素に付与することで、template
配下の要素をまとめて消したりできる。
v-show
は消えたように見えるだけ。切り替え頻度が高いものはこちらを使うのが良い。
v-else-if="式"
, v-else
いわゆるelseif
節やelse
節に相当。
データバインディング
属性のデータバインディング(v-bind:属性名="属性値の式"
)
<div key="id">
: “id” という文字列
<div v-bind:key="id">
: id
というJavaScriptの変数の値が展開される
以下はどちらで書いても同じ
<div v-bind:key="id">
<div :key="id">
直値で与えた属性と併用する例。
1
2
3
|
<!-- dataオプションのurlプロパティは展開される。 -->
<!-- 直値で与えた属性と同じ名前のプロパティを指定する場合、プロパティの値で上書きされる -->
<a v-bind:href="url" href="https://default.com" target="_blank">URL</a>
|
クラスとスタイルのデータバインディング(v-bind:class="{属性名: 条件式, ...}"
, v-bind:style="{属性名: 式, ...}"
)
オブジェクトや配列をバインドすると、クラス名やCSSプロパティとして展開される。
1
2
|
<p v-bind:class="{a: isA, b: isB, c: isC }">
<p v-bind:style="{color: textColor, backgroundColor: bgColor}">
|
1
2
3
4
5
6
7
8
9
10
|
var app = new Vue({
el: '#app',
data: {
isA: true,
isB: false,
isC: true,
textColor: 'blue',
bgColor: 'green',
}
})
|
これで動かすと
1
2
|
<p class="a c">
<p style="color: blue; backgroundColor: green;">
|
となる。
class
で指定する式は条件式として評価され、真となる値だけが残るようだ。
style
の方は値が文字列として展開されるようだ。
属性のデータバインディングと同じように、以下はどちらで書いても同じ。
<p v-bind:class={...}>
<p :class={...}>
オブジェクトデータを直接渡すこともできる。
1
|
<p v-bind:style="styleObject">
|
1
2
3
4
5
6
7
8
9
|
var app = new Vue({
el: '#app',
data: {
styleObject: {
color: 'blue',
backgroundColor: 'green'
}
}
})
|
まとめてバインディングする(v-bind="評価するとobjectになる式"
)
以下はどちらで書いても同じ。
<img v-bind:src="item.src" v-bind:alt="item.alt" v-bind:width="item.width" v-bind:height="item.height">
<img v-bind="item">
一部だけ書き換えることもできる。
1
|
<img v-bind="item" v-bind:width="item.width*2">
|
イベントハンドリング(v-on:click="評価するとfunctionになる式"
, v-on:click="式"
)
v-on:click
ボタンクリック時
v-on:load
画像などの読み込みが完了したとき
v-on:scroll
要素のスクロールイベント
v-on:mousewheel
マウスホイールイベント
v-on:dragstart
ドラッグ
v-on:input
inputに値が入力された
1
2
3
4
|
<button v-on:click="handleClick">Click</button>
<!-- 引数を与える場合は()で指定する。ループの中でインデックスを与えたりして使う -->
<button v-on:click="bracket(42)">Click</button>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var app = new Vue({
...
methods: {
handleClick: function (event) {
alert(event.target)
},
bracket: function (idx) {
// 配列の要素を置き換える場合は $set を使う必要がある。
// 代入すると配列要素が更新されただけになってしまうため、再描画されない。
// $set は値をリアクティブデータとして追加する。
this.$set(this.array, idx, "[" + this.array[idx] + "]")
}
}
})
|
以下はどちらで書いても同じ。
<button v-on:click="bracket(42)">Click</button>
<button @click="bracket(42)">Click</button>
もとのイベントはこんな感じで拾える。
1
|
<button v-on:click="handleClick($event)">Click</button>
|
1
2
3
4
5
6
7
|
var app = new Vue({
...
handleClick: function (event) {
alert(event.target)
},
...
})
|
イベントの修飾子についてはこちら
コンポーネントはv-on
でイベントをハンドルできるが、window
やbody
についてはaddeventListener
メソッドで登録しておく必要がある。
フォーム入力バインディング(v-model="リアクティブデータの変数"
)
input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成し、指定したリアクティブデータの変数に入力値がセットされる。
1
2
|
<input v-model="out" />
<input v-model="round" type="range" min="0" max="50" />
|
v-model
を使わずに、v-bind
やv-on
で頑張ることもできる。自分でバリデーションしたいときはこっち?
1
|
<input v-bind:value="out" v-on:input="handleInput" />
|
1
2
3
4
5
6
7
8
9
10
11
12
|
var app = new Vue({
...
methods: {
// 10文字未満に制限する
handleInput: function (event) {
if (event.target.value.length < 10) {
this.out = event.target.value;
}
},
}
...
})
|
いろいろ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<!-- 文字列 -->
<input v-model="out" />
<!-- 複数行であっても文字列 -->
<textarea v-model="out" />
<!-- v-model.numberとすることで、プロパティに数値として値を設定できる -->
<input type="range" v-model.number="r">
<!-- "#008000" みたいな文字列 -->
<input type="color" v-model="col">
<!-- ラジオボックスは文字列 -->
<input type="radio" value="フシギダネ" v-model="poke"> フシギダネ キミにきめた! </label>
<input type="radio" value="ヒトカゲ" v-model="poke"> ヒトカゲ キミにきめた! </label>
<input type="radio" value="ゼニガメ" v-model="poke"> ゼニガメ キミにきめた! </label>
<!-- 択一なselectは文字列 -->
<select v-model="out">
<option disabled="disabled">以下から選択!</option>
<option value="a"> A </option>
<option value="b"> B </option>
<option value="c"> C </option>
</select>
<!-- 複数選択のselectは配列 -->
<select v-model="out" multiple>
<option disabled="disabled">以下から選択!</option>
<option value="a"> A </option>
<option value="b"> B </option>
<option value="c"> C </option>
</select>
<!-- チェックボックスはBoolean。値を指定することもできる -->
<input v-model="out" type="checkbox" />
<input v-model="out" type="checkbox" true-value="真" false-value="偽" />
|
複数のチェックボックスは配列にもできる
1
2
3
4
5
6
|
<label><input v-model="array" type="checkbox" value="a"> A </label>
<label><input v-model="array" type="checkbox" value="b"> B </label>
<label><input v-model="array" type="checkbox" value="c"> C </label>
<!-- AとCにチェックを入れると ["a", "c"] になる -->
<p>{{array}}</p>
|
1
2
3
4
5
6
7
|
var app = new Vue({
...
data: {
array: []
}
...
})
|
修飾子についてはこちら
数値に変換したり(.number
)、余白を取り除いたり(.trim
)できる。
なお、画像などのtype="file"
はv-model
を使えないので、change
イベントをハンドルする。
1
|
<input type="file" v-on:change="handleChange">
|
カスタムディレクティブ
v-
から始まるディレクティブを自分で作れるらしい。
算出プロパティ
計算をキャッシュしておいて、{{halfWidth}}
のように参照できる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var app = new Vue({
computed: {
halfWidth: function(){
...
return width/2.0;
},
halfRect: function(){
return {
w: halfWidth,
h: width*0.6 / 2.0
}
}
}
})
|
functionの中でリアクティブデータを参照している場合、値が変更されると再計算される。
計算コストの高い処理結果をキャッシュするのに使える。例えばリストの絞り込み結果とか。
監視
1
2
3
4
5
6
7
|
var app = new Vue({
watch: {
監視対象のリアクティブデータやプロパティ: function(新しい値, 古い値){
...
},
}
})
|
フォームの入力を監視して、値が変更されたら検索APIコールして・・・みたいな使い方で動的検索ができる。
フィルタ
1
2
3
4
5
6
7
8
9
10
|
var app = new Vue({
filters: {
bracket: function(v){
return "[" + v + "]"
},
upper: function(v){
return v.toUpeerCase()
}
}
})
|
1
|
<p>{{ str | upper | bracket }}</p>
|
methodsとよく似ているが、文字列を加工するときは便利。
パイプみたいになっていて、左から右へデータが流れていく。
コンポーネント
JavaScriptとテンプレート(HTML, CSS)を1つにまとめたもの。帰納単位に分離して開発する仕組み。
コンポーネントはWebにも転がっている
awesome-vue
https://github.com/vuejs/awesome-vue
Vue Curated
https://curated.vuejs.org/
element
https://github.com/ElemeFE/element
紹介記事: https://s8a.jp/vue-js-library-element
Onsen UI
https://ja.onsen.io/
モバイル向けのUIを簡単につくれる。良さそう。
コンポーネントを自作する
グローバルに登録
1
2
3
4
5
6
7
8
9
10
11
|
Vue.component('new-component', {
template: '<p>New Component!!</p>',
data: function(){
return {
...
}
},
methods: {
...
}
})
|
1
2
3
4
5
|
<div id="app">
<!-- どのVueテンプレートからでも参照できる -->
<new-component></new-component>
<!-- <p>New Component!!</p> として表示される -->
</div>
|
new Vue()
と同じように定義する。ただし、data
はObjectを返すfunctionになっていなければならない。
template
で指定するhtmlはツリーになってないとだめ。ルートは1つ。<div>
とかでくくっておくのがよい。
- コンポーネントはネストできる。
ローカルへの登録
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var newComponent = {
template: '<p>New Component!!</p>'
data: function(){
return {
...
}
},
methods: {
...
}
}
var app = new Vue({
el: '#app',
components: {
//これで<new-components>が使用できるようになる
'new-components': newComponents
}
})
|
基本的にローカル登録にしておいてスコープを絞っといたほうがいい。
コンポーネント間の通信
ネストしたコンポーネントの親から子(props down)
静的に値を渡す。引数の値渡しみたいな感じ?
1
2
3
|
<!-- 親のテンプレート -->
<child name='Mario', age=10></child>
<child name='Luigi', age=8></child>
|
1
2
3
4
5
6
7
8
9
10
11
|
Vue.component('child', {
template: '<p>{{name}}({{age}})</p>',
props: ['name', 'age'] //ここで受け取る属性を指定する
// 型指定することもできる
// props: {
// name: String
// age: Number
// }
})
|
リアクティブデータを渡す場合
1
|
<child v-bind:name='Mario', age=10></child>
|
これで、親側でname
を変更すると、子のnameが変更される。
ネストしたコンポーネントの子から親(event up)
1
2
|
<!-- 親 -->
<child v-on:child-event="parentsHandler"></child>
|
1
2
|
// 子
this.$emit('child-event')
|
$emit('イベント名', 引数,...,)
を使う。子要素のボタンを押したときに、親要素をどうこうしたいときに。
親子で双方向のデータバインディングする(v-model
)
1
2
3
4
5
|
<!-- 親 -->
<child v-model:"date"></child>
<!-- 上記は以下の糖衣構文 -->
<child v-bind:value="date" v-on:input="date = $event"></child>
|
1
2
3
|
// 子から以下のように$emitすることで、dateが自動で更新される。
// v-modelで子へ渡されるプロパティはvalueなので、子側のpropsに指定するのを忘れないように!
this.$emit('input', '20210-04-28')
|
いくつもバインドする場合は .sync
修飾子を使う。
親子ではないコンポーネント間
1
2
3
4
5
6
7
8
|
var bus = new Vue() //イベントバス。
var bus = new Vue({
data: {
... //値をもたせることもできる。
}
})
|
というのを作っておいて
1
2
3
4
|
// 受け取る方
bus.$on('何らかの-event', function(){
...
})
|
1
2
|
// eventを発行する方
bus.$emit('何らかの-event')
|
スロット
親コンポーネントから、子コンポーネントのテンプレートの一部に何かを差し込むのに使う機能。
1
2
|
<!-- 子のテンプレート -->
<p>ここに入る→ <slot></slot><p>
|
1
2
3
4
5
|
<!-- 親 -->
<child>これを入れる</child>
<!-- するとこんな感じになる -->
<p>ここに入る→ これを入れる</p>
|
<slot>
タグが置き換わる。 <slot name=''>
とすることで複数のスロットを名前付きで挿入することができる。
ほかにもいろいろ使えるらしい。
Vueでアプリを作る
- Vuex: 状態管理用ライブラリ。複数コンポーネントでのデータ共有や、アプリの状態の一元管理を行う。
- Vue Router: 複数の画面とURLを紐付ける、SPA構築用ライブラリ
Vue CLI
アプリ構築用ツール郡のCLI。
かんたんにwebpackで単一ファイルコンポーネントを作ることができる。
webpackとは
内部ではBabel使ってるらしい。
プロジェクトを作る
参考: Creating Project
1
2
3
4
|
$ vue create <プロジェクト名>
# 対話形式で色々設定する。 `vue ui` でブラウザから同様のプロジェクト作成を行える。
$ cd <プロジェクト名>
$ npm run serve
|
http://localhost:8080/ にアクセスすると、アプリ画面が表示される。
参考: Vue CLI3について
ビルド設定のカスタマイズは、プロジェクトルートに vue.config.js というファイル作成してそこに追加していきます。
単一ファイルコンポーネント(.vue
ファイル)
単一ファイルコンポーネント(SFC, Single File Components) として、 HTML/JavaScript/CSSをまとめた .vue
ファイル単位で管理する。
index.html
index.js
1
2
3
4
5
6
7
8
9
10
|
import Vue from "vue";
import App from "./Hello";
Vue.config.productionTip = false;
new Vue({
el: "#app",
template: "<App/>",
components: { App }
});
|
Hello.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<template>
<p>{{ greeting }} World!</p>
</template>
<script>
export default {
data: function() {
return {
greeting: "Hello"
}
}
};
</script>
<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>
|
<template>
: コンポーネントのテンプレート部分。必要なパッケージ入れて lang="jade"
とすると Jadeで書けたりする。
<script>
: コンポーネントのテンプレート以外の部分。
<style>
: CSSの部分。scoped
にすると、このコンポーネントとその配下のコンポーネントで有効になる。こちらもlang=stylus
でStylusで書けたりする。
Vuex
Vuexとは何か?
Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリ
あなたのアプリがシンプルであれば、Vuex なしで問題ないでしょう。
コンポーネント横断で使えるリアクティブなグローバル変数群?
基本
src/store.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
// stateを変更するための関数群。
// state.commitしたときに呼ばれる
increment: state => state.count++,
setCount:(state, payload){
state.count = payload
}
},
getters: { // stateの値を取得するための関数群
count(state, getters, rootState, rootGetter){ //getters経由で他のgetterを使うこともできる。
return state.count
},
isLarge(state){ // getters等は受け取っても受け取らなくてもよい
// 引数をとるGetterも作れる。 state.getters.isLarge(42) のようにコールする
return op => state.count > op
}
}
})
export default store
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import store from '@/store.js' // @はsrcのエイリアス
store.state.count // -> 0
state.getters.count // -> false
state.getters.isLarge(0) // -> false
store.commit('increment')
store.state.count // -> 1
state.getters.count // -> 1
state.getters.isLarge(0) // -> true
store.commit('setCount', 42)
store.state.count // -> 42
|
Vuexのドキュメントをみたところ、ストアの値をフィルタリングする場合や、共通の処理をコンポーネント間で共有したい場合に、getterを使っているようだ。
複数のコンポーネントで使う場合はnew Vue
するときに指定する
src/App.vue
1
2
3
4
5
6
|
import store from './store.js'
new Vue({
...
store,
...
})
|
適当なコンポーネント.vue
1
2
3
4
5
6
|
export default {
craeted() {
console.log(this.$store.state.count)
this.$store.commit('increment')
}
}
|
this.$state
でアクセスする。
引数を取るgetterは結果がキャッシュされないので、コンポーネントの算出プロパティに入れておくとよい。
適当なコンポーネント.vue
1
2
3
4
5
6
7
8
9
10
|
export default {
data: {
current: 42
}
computed: {
isLarge(){
return this.$state.getters.isLarge(this.current)
}
}
}
|
mutation は satateを変更する唯一の方法。
actionsとdispatch
src/store.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
const store = new Vuex.Store({
state: {
searchResult: []
},
mutations: {
setSearchResult(state, payload){
state.searchResult = payload
}
},
getters: {
actions: {
// 非同期処理用。commitをコールして値を変更するのに使う。
// 非同期にストアに書き込む(検索結果をストアするとか)ときに使うのかな
//state.dispatchしたときに呼ばれる
search({ commit }, payload){
axios.get('/api/search', {param:{ query: payload}}).then(res, {
commit('setSearchResult', res.body)
})
},
}
}
})
export default store
|
1
|
$store.state.dispatch('search', 'ピカチュウ')
|
Vue Router
コンポーネントとURLを紐付けてコンテンツを生成し、SPAを構築するための拡張ライブラリ。
Vue Router
Vue Router は Vue.js 公式ルータです。これは Vue.js のコアと深く深く統合されており、Vue.js でシングルページアプリケーションを構築します。機能は次の通りです:
・ネストされたルート/ビューマッピング
・モジュール式、コンポーネントベースのルータ構造
・ルートパラメータ、クエリ、ワイルドカード
・Vue.js の transition 機能による、transition エフェクトの表示
・細かいナビゲーションコントロール
・自動で付与される active CSS クラス
・HTML5 history モードまたは hash モードと IE9 の互換性
・カスタマイズ可能なスクロール動作
準備
1
|
$ npm install vue-router
|
使ってみる
慣例として、ルートに直接紐づくコンポーネントはviews
やpages
のようなディレクトリにまとめることが多い。
src/views/Home.vue
1
2
3
|
<template>
<h1>Home</h1>
</template>
|
src/views/Product.vue
1
2
3
|
<template>
<h1>Product</h1>
</template>
|
src/router.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import Product from '@/views/Product.vue'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ path: '/product', component: Product },
]
})
export default router
|
src/main.js
1
2
3
4
5
6
7
8
9
10
11
|
import Vue from 'vue'
import App from './App.vue'
import router from '@/router.js'
Vue.config.productionTip = false
new Vue({
router: router,
render: h => h(App),
}).$mount('#app')
|
src/App.vue
1
2
3
4
5
6
7
8
9
|
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link>
<router-link to="/product">Product</router-link>
</nav>
<router-view />
</div>
</template>
|
これで、 http://localhost:8080 にアクセスすると、“Home” と “Product” リンクが生成されて、クリックするとそれぞれのコンポーネントが <router-view />
の位置に表示される。
ハッシュモードとヒストリーモード
HTML5 History モード
vue-router のデフォルトは hash モード です - 完全な URL を hash を使ってシミュレートし、 URL が変更された時にページのリロードが起きません。
history モードを使用する時は、URL は “普通” に見えます e.g. http://oursite.com/user/id 。美しいですね!
ヒストリーモードを有効にするには、VueRouter
のmode
プロパティを'history'
にして、サーバの設定を変更する必要がある。
src/router.js
1
2
3
4
|
const router = new VueRouter({
mode: 'history',
routes: [...]
})
|
nginxの例
1
2
3
|
location / {
try_files $uri $uri/ /index.html;
}
|
その他サーバの設定方法はこちら参照。
ルート定義のオプション
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
...
const router = new VueRouter({
mode: 'history',
routes: [
{
name: 'prod',
path: '/product/:id', //URLからパラメータを受け取る場合はこう書く。
//path: '/product/:id(\\d+)', //正規表現で数字のみにマッチさせることもできる
compnent: Product,
meta: { requiresAuth: true } //認証が必要な場合はtrue
},
{
name: 'prodlist',
path: '/product', // パラメータなしの場合はrouteを別にすることもできる。
compnent: ProductList,
meta: { requiresAuth: true } //認証が必要な場合はtrue
}
],
// リダイレクトの設定。リダイレクト先はパスまたは名前で指定する
{ path: '/a', redirect: '/product' },
{ path: '/a', redirect: { name: 'prod'} },
})
|
受け取ったパラメータはこのように参照する。
1
2
|
this.$route.params.id //URLパラメータを受け取る
this.$route.query.id //クエリパラメータを受け取る場合はこう
|
$route
は マッチしたルートの情報が入ったオブジェクト。
ちなみに、パラメータは$route
ではなくpropsとして受け取っておいたほうがいい。ユニットテストがしやすいので。
src/Product.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<template>
<div>
<h1>Product</h1>
<div v-if="pid">Param: ID:{{pid}}</div>
<div v-if="qid">Query: ID:{{qid}}</div>
</div>
</template>
<script>
export default {
props: {pid: Number, qid: Number} //受け取るパラメータ
}
</script>
|
src/router.js
1
2
3
4
5
6
7
8
9
10
11
12
13
|
...
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ path: '/product', component: Product },
{
path: '/product/:id(\\d+)',
component: Product,
props: r => ({ qid: r.query.id, pid: r.params.id}) //こんな感じで。
},
]
})
...
|
ナビゲーション
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<template>
<div id="app">
<!-- 基本はこれ -->
<router-link to="/product"></router-link>
<!-- テンプレートリテラルを使う場合。込み入った記述をするとき便利 -->
<!-- =より右をJavaScriptの式として評価したい場合は `:to` を使うっぽい -->
<router-link :to="`/product/${ id }`"></router-link>
<!-- aタグ以外で囲みたい場合は`tag`で指定する -->
<router-link to="/product" tag="button"></router-link>
<!-- objectで指定できる。パラメータを渡す場合に便利か -->
<!-- paramsを渡す場合は、routerにnameを登録しておく必要がある -->
<router-link :to="{ path: '/product' }"></router-link>
<router-link :to="{ path: '/product', query: { id: 42 } }"></router-link>
<router-link :to="{ name: 'prod', params: { id: 42 } }"></router-link>
</div>
</template>
|
アクティブなルートにマッチする router-link
には .router-link-exact-active
や .router-link-active
が付与されるので、簡単にハイライトできる。
1
2
|
.router-link-exact-active {background: #ff00ff} //完全一致したルート
.router-link-active {background: #ff00ff} //マッチしたパスを含むルート
|
コードから遷移するには $router
に対していろいろやる
1
2
|
this.$router.push('/product') // push, replace, go
this.$router.push({name: 'prod', params: { id: 1}}) //router-linkのようにパラメータを渡すこともできる
|
動的ルートのサンプル
ポケモン図鑑(Pokedex)を作る。データは src/DummyPokedex.js
にハードコードしたダミーを使う。
src/App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link>
<router-link to="/pokemon">Pokedex</router-link>
</nav>
<img alt="Vue logo" src="./assets/logo.png" />
<p>
<router-view />
</p>
</div>
</template>
<script>
export default {
name: "App"
}
</script>
|
src/api/DummyPokedex.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
const pokedex = [
{ number: 1, name: "フシギダネ", types: ["くさ", "どく"], height: 0.7, weight: 6.9 },
{ number: 4, name: "ヒトカゲ", types: ["ほのお"], height: 0.6, weight: 8.5 },
{ number: 7, name: "ゼニガメ", types: ["みず"], height: 0.5, weight: 9.0},
]
//ダミーデータとアクセス用のAPIを定義する
export default {
fetchAll() { return pokedex },
findFirst(number) { return pokedex.find(p => p.number == number) },
// DB等から読み込んでるように振る舞わせるため、`setTimeout` で2秒後にコールバックする
asyncFindFirst(number, callback) {
setTimeout(() => {
callback(this.findFirst(number))
}, 2000) // 2000ms 後に callbackする
}
}
|
src/router.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import Pokedex from '@/views/Pokedex.vue'
import Pokemon from '@/views/Pokemon.vue'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ path: '/pokemon', component: Pokedex },
{
path: '/pokemon/:number(\\d+)',
component: Pokemon,
props: route => ({
number: Number(route.params.number)
})
},
]
})
export default router
|
src/views/Pokedex.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<template>
<div>
<h1>Pokedex</h1>
<ul>
<!-- ポケモン一覧をを表示する -->
<li v-for="{number, name} in pokedex" :key="number">
<router-link :to="`/pokemon/${number}`">No.{{number}} {{name}}</router-link>
</li>
</ul>
</div>
</template>
<script>
import Pokedex from '@/api/DummyPokedex.js'
export default {
computed: {
pokedex: () => Pokedex.fetchAll()
}
}
</script>
|
src/views/Pokemon.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<template>
<div>
<div v-if="pokemon">
<h1>No. {{pokemon.number}} {{pokemon.name}}</h1>
<h2>体長:{{pokemon.height}}[m] 重さ:{{pokemon.weight}}[kg]</h2>
<h2>タイプ</h2>
<p v-for="type in pokemon.types" :key="type">{{type}}</p>
</div>
<div v-else>
<h2>Loading...</h2>
</div>
</div>
</template>
<script>
import Pokedex from '@/api/DummyPokedex.js'
export default {
props: {number: Number},
data(){
return {pokemon: null}
},
// computed では this.numberを参照できないので、watch で numberを監視するように。
watch: {
number: {
handler(){
// 遅延読み込みした結果を格納する
Pokedex.asyncFindFirst(this.number, pokemon => {
this.pokemon = pokemon
})
}, immediate: true //ここがtrueじゃないと、すぐにhandlerが呼ばれない。
}
}
}
</script>
|
src/views/Pokemon.vue
の下に子コンポーネントを作る場合は、src/views/Pokemon/*.vue
とするのがいいらしい。
遷移アニメーション
1
2
3
4
5
6
7
8
9
10
11
12
|
<transition name="viewwww">
<router-view />
</transition>
...
<style>
.viewwww-enter-active {
transition: opacity 0.5s;
}
.viewwww-enter, .viewwww-leave-to {
opacity: 0;
}
</style>
|
name
に指定したviewwww
が一致してないといけない。
ナビゲーションガード
遷移を操作するためのフック。
to()
: 遷移後のrouteオブジェクトを受け取る
from()
: 遷移前のrouteオブジェクトを受け取る
next()
: これを呼ぶとフックが解決してrouteの遷移が行われる。遷移を中止する場合はnext(false)
をコールする。next('/pokemon')
などで任意のrouteにリダイレクトもできる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
const router = new VueRouter({
routes: [
...
{
path: '/pokemon',
component: Pokedex
beforeEnter(to, from, next){ //このrouteへ遷移する前に呼ばれる。
next() //遷移してもOKな場合に呼ぶ
}
},
...
]
})
//グローバルなガードも使える
router.beforEeach((to, from, next) => {
//コンポーネントガード解決前に呼ばれる
...
})
router.afterResolve((to, from, next) => {
//コンポーネントガード解決後に呼ばれる
...
});
router.afterEeach((to, from) => { // 遷移が終わってから呼ばれるのでnextは使えない。
//すべてのルートの遷移後に呼ばれる
...
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<script>
export default {
// beforeRoute{Enter, Update, Leave} が使える
beforeRouteEnter(to,from,next){
// この時点ではコンポーネントのインスタンスはまだ作られていないので、thisにアクセスできないことに注意。
next(vm => {
// vm には VueComponent が入っている
// このコールバックでthisにアクセスできる。
})
}
}
</script>
|
beforEeach
とかで、認証認可のチェックができる?
axios
axios を利用した API の使用
Promise ベースの HTTP クライアント
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const axios = require('axios').default
// Make a request for a user with a given ID
axios.get('/user?ID=12345')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
|
UI FWを導入
BootstrapVue
1
|
$ npm install bootstrap-view
|
src/main.js
1
2
3
4
5
6
|
...
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
...
|
これでOK
Firebaseにデプロイ
1
2
3
4
5
6
7
8
9
10
11
|
$ ls
README.md node_modules package.json src
babel.config.js package-lock.json public vue.config.js
$ firebase login
$ firebase init
# Hostingを選択
# ? What do you want to use as your public directory? public
# ? Configure as a single-page app (rewrite all urls to /index.html)? Yes
# ? File public/index.html already exists. Overwrite? No
|
.firebaserc
はデプロイ先のプロジェクト名とかが入っているので、 .gitignore
に加えておくと良い。
firebase.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
{
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
|
Vueはdistにデプロイ用のもろもろを吐き出すので、"public":"public"
から "public":"dist"
に変更しておく。
ビルドしてデプロイする
1
2
|
$ npm run build
$ firebase deploy
|
ES2015(ES6)関連
文法いろいろ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
// 変数宣言
// var x = 0 //スコープ広い
let x = 0 //スコープ内でのみ有効
const y = 42 //再代入不可
// function
new Vue({
methods: {
// 以下は handle: function(){ ... } と同じ。
// プロパティとして関数を宣言するときは function を省略できる。
handle(){ ... }
}
})
//アロー関数
let hoge = () => "hoge"
let fuga = arg => "fuga and " + arg
let piyo = (arg1, arg2) => {
let joined = arg1 + "piyo" + arg2;
return joined;
}
//テンプレートリテラル
let template = `
<div class="sample">
<p>${ this.variable }</p>
</div>`
// オブジェクトプロパティのショートバンド
let short = {
hoge, fuga // 変数名がプロパティの名前になる
}
// let short = {
// hoge: hoge,
// fuga: fuga
// }
// 多重代入(分割代入)
let [a,b] = [1,2] // aに1, bに2が代入される
let { a } = {a:'A', b:'B', c:'C'} // a に 'A'が 代入される
// スプレッド演算子
let args = [1,2]
piyo(...args) // piyo(1,2)と同義
let args_ex = [ ...args, 3] // [1,2,3]
|
モジュール定義とインポート
Sample.js
1
2
3
4
5
|
var state = {
count: 1
}
export default state
|
export default state
は、デフォルトのimport文で呼ばれたときに返すデータを定義している。
1
|
import Sample from './Sample.js'
|
みたいに書くと、stateのオブジェクトをSampleという変数名としてインポートする。
export default
vs module.exports =
こちら参照
・基本的にはexport defaultを使う
・複数のモジュールをexportするときはexportを使う
・受け取り側はimportを使う
require
vs import
こちら参照
モジュール読み込みの仕様の主要なものとして、ESM (ECMAScript Modules)とCJS (CommonJS Modules)があります。
importを使うのがESM方式で、requireを使うのがCJS方式
require文は、CommonJSの仕様で、Nodejsの環境で動作してくれる書き方です。Nodejs環境ということはつまり、サーバサイドでの実行ということになります。
import を使うのが良さそう。
メモ
開発環境
Ubuntu(WSL)環境にNode.jsをインストールする
1
2
3
4
5
|
$ sudo apt update
$ sudo apt install nodejs npm
$ sudo npm install n -g
$ sudo n stable
$ # sudo apt purge -y nodejs npm #古いのは消すex
|
Vue CLI
1
2
3
|
$ sudo npm install -g @vue/cli
$ vue --version
@vue/cli 4.3.1
|
VS Codeに入れた拡張
- ESLint
- Prettier
- Veture (主な使い方はここ。このあたりも参考に。)
- Vue Peek
- Auto Rename Tag
いろいろ