Essentials
Vue.jsってこういうもの
Vue.jsは、独自のテンプレート構文とVueインスタンスでDOM(Document Object Model)を描画することができるJavaScriptのフレームワークである。
データに変更があれば描画は自動で更新され、何らかのアクションを起こしたり、挙動に変化があったりする。こういったデータありきで動く仕組みをデータバインディングという。
Vue.jsでは「何を」「どうしたいか」を記述すればDOMが構築される仕組みになっていて、どうやってするかはコンパイラ(Vue.jsの本体)が勝手にやってくれるので気にしなくてもいいらしい。
インストール
Vue.jsの使用するには、本体をnpmでインストールするか、専用の開発環境を構築するか、CDN版を利用する方法がある。今回は一番手軽なCDN版でVue.js v2から学習を進めていく(v2は2023年12月31日でサポートが終了するので、追ってv3の学習も入れていく)。
https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js
Vue.js基本的な書き方
Vueを始めるにはまず、Vueオブジェクトからnew演算子を使ってVueインスタンスを生成する。以降、これはルートインスタンス(ルート Vue インスタンス)と呼ばれる。
const vm = new Vue({
// オプション
})
上記では変数に代入しているが、しなくても使用可能。複数のVueインスタンスを使用する場合や、そのVueインスタンスの持つメソッドを外部から呼び出すなどしたいときには任意の変数に代入するらしい。
コンストラクタの引数には、オブジェクト表記であらかじめ定められているオプションを持たせる。オプションとはelオプション、dataオプション、methodsオプションなどインスタンスの中で使用するデータや関数など様々なプロパティのことを指す。
const vm = new Vue({
el: #App,
data: {
msg : 'Hello Vue.js'
}
})
インスタンスで登録したデータをhtmlに反映するにはテンプレートを使用する。
// テンプレート
<div id="App">
<p>{{msg}}</p>
<p>{{ msg + '!!!' }}</p>
<div>
最も基本的なテンプレートは上記のMustache(マスタッシュ)構文。登録したデータをテキストとして表示するときに使用する。
二重括弧({{}})の中にdataオプションで定義したプロパティを入れるとその値が表示される、また値に「+」演算子を使って文字列を連結するなどの式も記述可能。
// 描画 <div id="App"> <p>Hello Vue.js/p> <p>Hello Vue.js!!!</p> <div>
上記の例を「何を」「どうしたいか」に当てはめると、「データmsgの値を、pタグのテキストとして表示したい」といった具合になる。
Mustacheには他に関数や後述する算出プロパティも使用できるので、複雑な式はそちらを使うことが主流。
オプション
Vueインスタンスの引数で使用するデータやメソッドのこと。
| el | DOMの描画を行うエリアを指定するもの。クラスかID名で指定をする。ただしクラスの場合は同クラス名の要素が重複していると使用できないので、基本的にはID名を使ったほうがいい。 |
|---|---|
| data | データの登録を行う。数値、文字列、配列、連想配列、真偽値を入れられる。 |
| computed | 算出(された)プロパティと呼ばれる。dataの持つ値に何らかの加工をして結果を返す(return)もの。computedの関数内で扱う値に変更があると自動的に再度処理が走る仕様。プロパティ扱いなので、呼び出すときに「()」は付けない。 |
| watch | 監視(する)プロパティ。対象プロパティの値に変更があった場合、定義した関数を実行。関数の第一引数に更新後の値、第二引数に更新前の値を渡すことができる。これも呼び出すときに「()」は不要。 |
| methods | 関数(メソッド)。呼び出されるたびに際限なく毎回処理が走る。関数なので呼び出すときには「関数名()」となる。 |
| filters | テンプレートにバインドしたテキストの変換処理のみを行う。mustacheもしくはv-bindの値の末尾に「|」でフィルター名をフィルター名を繋げて記述することで呼び出せる。Vue 3では廃止。 |
| ライフサイクルフック | 登録した処理を、特定のタイミングで自動的に呼び出す関数。例えば、インスタンスが作成されオプションのセットアップ処理が完了したタイミングで呼び出す「created」などがある。 |
ディレクティブ
ディレクティブとは、v-を接頭辞にした特別な属性で、Vue本体に「こうしたい」という何らかの指示をするテンプレート構文の一種。
| v-bind | データをDOMの属性値(style、src、alt、class他)に紐づける。 |
|---|---|
| v-for | データオプションに登録した配列や連想配列から値を取り出し要素を繰り返し描画する。 |
| v-on | clickやchangeなどのイベントが発生した時になんらかの処理を紐づけ実行する。 |
| v-show | 表示の切り替えができる。値がfalseの時は、要素に「display:none」が自動で付加される。 |
| v-if | 要素の表示非表示を切り替える点はv-showと同じ。値がfalseの場合、こちらはDOMから要素が削除される。if文による条件分岐なので「v-else-if」「v-else」と合わせて複数の分岐も可能。 |
| v-model | 双方向データバインディング。データとフォームの入力項目を紐づけ、入力された値を即座にDOMに反映できる。 |
書き方
データの紐づけ(v-bind)
<p v-bind:style="styleColor" v-bind:class="addClass">Hello {{ message }} !</p>
<input type="text" v-bind:value="date">
---------------------------
new Vue({
el: '#App',
data: {
message: 'World',
styleColor: {
'color': '#f00', 'font-weight': 'bold'
},
addClass: 'test-class',
date: new Date().toLocaleString()
}
})
Hello {{ message }} !
引数
v-bindを含む一部のディレクティブ名の後ろにコロンで繋げた値をvueでは「引数」と呼ぶ。v-bindはhtmlの属性(style、src…)、v-onではDOMイベント名(click、submit…)などディレクティブによって値はそれぞれ。
クラスとスタイルのバインディング
v-bindは基本的には属性値と値をバインドするためものだが、style、class属性に対してv-bindを用いるときは文字列のほかに、オブジェクトまたは配列も利用できる。
詳しくは公式を参考にすること。
要素を繰り返し描画(v-for)
<ul>
<li v-for="(item, index) in list" v-bind:key="item">{{item}}: index番号{{index}}</li>
//(変数名,index番号)in オブジェクト名
//index不要な時は()も取る。変数名 in オブジェクト名
</ul>
---------------------------
new Vue({
el: '#App',
data: {
list: ['spring(春)','summer(夏)','autumn(秋)','winter(冬)']
}
})
- {{item}}: index番号{{index}}
key属性が必需品
v-forを使用する際は、必ずv-bindを使ってkey属性を指定する必要がある。この必要性は下記のkeyのないリスト、あるリストのテキストボックスにそれぞれ左から順に1~3の数字を入れて、「先頭を削除」ボタンをクリックするとわかる。
<ul>
<li v-for="(item,i) in colors">{{item}}: <input type="text"></li>
</ul>
<ul>
<li v-for="(item,i) in colors" v-bind:key="item">{{item}}: <input type="text"></li>
</ul>
<button @click="removeColor">先頭を削除</button>
---------------------------
new Vue({
el: '#App',
data: {
colors: ['あか','あお','きいろ']
},
methods: {
removeColor(){
// 配列の先頭を削除
this.colors.shift();
}
}
})
key設定がないリスト
- {{item}}:
「v-bind:key="item"」でkey設定があるリスト
- {{item}}:
上記で正しい結果(先頭の要素が削除される)が得られるのはkeyの設定がある方で、ない方は先頭ではなく最後尾の要素が消えてしまう。これはvueの特性によるものらしいが、正直言ってることが理解できず…。
おそらくv-forで描画された要素が更新される際、すべてを再描写するのではなく使える部分は再利用するということだと思う。
上記の例でいうと、配列の先頭のデータが削除され、3回描画されていたものが2回になるが、DOM上のinputタグには変更箇所はなく再利用され2つ目までのものが残る。結果、DOM上リストの最後尾が削除されているように見えてしまう。
こういったちぐはぐな現象を回避するために、keyの設定が不可欠となる。keyの設定は配列とDOM要素を紐づける役割があり、配列が削除されるとそれに紐づいた要素のみが削除され、残り2つの要素には変更はないので再描画もなく意図した結果が得られる。
データの即時反映(v-model)と条件分岐(v-show)
<ul>
<li v-for="todo in todos" v-bind:key="todo.id">
<label>
<input type="checkbox" v-bind:checked="todo.status" v-model="todo.status">
{{todo.text}}
<strong v-show="todo.status">◎</strong>
</label>
</li>
</ul>
<div>todos:[<p v-for="todo in todos" v-bind:key="todo.id">{{todo}}</p>]</div>
---------------------------
new Vue({
el: '#App',
data: {
todos:[
{id:1, status: true, text: 'Vue.jsの学習'},
{id:2, status: false, text: '部屋の片づけ'}
]
}
})
{{todo}}
]v-modelとは
v-modelは実は入力と反映の2つの処理をひとつにまとめて簡潔にしている糖衣構文(シンタックスシュガー)である。
<input v-model="searchText">
{{searchText}}
{{searchText}}
これ↑を省略せずに書くと下のようになり、v-onでinputイベントが起きたとき入力値をデータに代入し、そのデータをプロパティとしてv-bindでvalue属性に紐づけている。
<input v-bind:value="searchText" v-on:input="searchText = $event.target.value">
2つのフォームが同じものという証拠に、どちらかにテキストを入力すると同時にもう一方にも同じ内容が入力され、Mustache構文でテキストがDOMに反映される。
このようなデータの入力と即時反映が自動化されたものを「双方向データバインディング」と呼ぶ。
v-modelのプロパティとイベント
v-modelはフォーム要素の種類によってそれぞれ定型のプロパティとイベントを使用している。
- テキストボックスとテキストエリアは、valueをプロパティ、inputをイベントとして使用。
- チェックボックスとラジオボタンは、checkedをプロパティ、changeをイベントとして使用
- セレクトボックスはvalueをプロパティ、changeをイベントとして使用。
複数の条件分岐(v-if)とイベント感知(v-on)
<p v-if="counter > 5" v-bind:style="styleColor">counterの値が6以上(true)の場合</p>
<p v-else-if="counter < 0">counterの値が0以下の場合</p>
<p v-else>counterの値が5以下(false)の場合</p>
<button type="button" v-on:click="counter++">カウントアップ</button>
<button type="button" @click="counter--">カウントダウン</button>
//v-on:イベント名="式(またはメソッド名)"
//v-on:は@に省略可。
<p>[counter = {{counter}}]</p>
---------------------------
new Vue({
el: '#App',
data: {
counter: 0,
styleColor: 'color: #f00'
}
})
counterの値が6以上の場合
counterの値が0以下の場合
counterの値が5から0の場合
[counter = {{counter}}]
イベントハンドリング
v-onディレクティブは、:(コロン)の後にイベント名を指定し、メソッドをバインドするメソッドイベントハンドラと、短いJavaScriptの式を記述するインラインメソッドハンドラがある。
<template>での複数要素のグループ化
v-ifまたはv-forで複数の要素をまとめて描画したいときは<template>タグを使用する。
<dl class="u-mb1">
<template v-if="ok">
<dt class="c-fontB">◆材料</dt>
<dd>A.砂糖…大さじ3</dd>
<dd>A.水…小さじ2</dd>
<dd>つまようじ…数本</dd>
</template>
<template v-else>
<dt class="c-fontB">◆作り方</dt>
<dd>
<ol>
<li>①耐熱容器にAの材料を入れて軽く混ぜてから電子レンジ(600W)で色がつくまで加熱(2~3分)。</li>
<li>②レンジから取り出し、平らなところに敷いたアルミホイルの上にスプーンですくって水玉をつくるように落としていく。</li>
<li>③熱いうちに水玉1つに1本のつまようじをセットして、固まったら完成。</li>
</ol>
</dd>
</template>
</dl>
<button @click="ok = !ok">表示切替</button>
---------------------------
new Vue({
el: '#App',
data: {
ok: true
}
})
- ◆材料
- A.砂糖…大さじ3
- A.水…小さじ2
- つまようじ…数本
- ◆作り方
-
- ①耐熱容器にAの材料を入れて軽く混ぜてから電子レンジ(600W)で色がつくまで加熱(2~3分)。
- ②レンジから取り出し、平らなところに敷いたアルミホイルの上にスプーンですくって水玉をつくるように落としていく。
- ③熱いうちに水玉1つに1本のつまようじをセットして、固まったら完成。
何らかのアクションが起こったときの処理(methods)
<ul v-bind:style="styleList" class="u-mb1">
<li v-for="(item,i) in items" v-bind:key="item.id">
{{item.name}}:在庫{{item.stock}}
<button v-if="item.stock" v-on:click="saleStock(i)">買う</button>
<span v-else>完売しました</span>
</li>
</ul>
<label>品名:<input type="text" v-model="newItemName"></label>
<label>仕入数:<select v-model="newItemStock">
<option value="5">5</option>
<option value="10">10</option>
<option value="15">15</option>
<option value="20">20</option>
</select></label>
<button v-on:click="addItem">リストに追加する</button>
---------------------------
new Vue({
el: '#App',
data: {
items: [
{id:1, name:'チーズケーキ', stock:3},
{id:2, name:'クリームパン', stock:10},
{id:3, name:'麩まんじゅう', stock:1}
],
newItemName: '',
newItemStock: '10'
},
methods: {
addItem: function() {
var max = this.items.reduce(function(a,b){
return a.id > b.id ? a.id : b.id;
},0)
if(this.newItemName){
this.items.push({
id: max + 1,
name: this.newItemName,
stock: this.newItemStock
});
}
},
saleStock(i){
if(this.items[i].stock > 0){
this.items[i].stock -= 1;
}
}
}
})
- {{item.name}}:在庫{{item.stock}} 完売しました
データに何らかの加工を施し使用する(computed)
<label v-for="item in filtersort" v-bind:key="item.id"><input type="checkbox" v-model="order" v-bind:value="item.price">{{item.name}} : {{item.price}}円</label>
<p>合計:{{totalPrice}}円(税込)</p>
---------------------------
new Vue({
el: '#App',
data: {
items: [
{id:1, name:'チーズケーキ', stock:3, price:370},
{id:2, name:'クリームパン', stock:10, price:200},
{id:3, name:'麩まんじゅう', stock:1, price:240},
{id:4, name:'タルト', stock:10}
],
order: [0],
tax: 8
},
computed: {
filtersort(){
const sweets = this.items.filter(function(val){
return val.price;
});
return sweets.sort((a, b) => a.price - b.price);
},
totalPrice(){
const total = this.order.reduce(function(a,b){
return a + b;
});
return Math.round(total + total * this.tax / 100);
}
}
})
合計:{{totalPrice}}円(税込)
データを常に監視し変更があれば処理をする(watch)
<input type="tel" v-model="number" v-bind:class="{'is-error': isError}">
<p v-bind:style="styleColor" v-if="isError">{{errorMsg}}</p>
---------------------------
new Vue({
el: '#App',
data: {
number :'',
pattern: /^[0-9]+$/,
isError: false,
errorMsg: '半角数字で入力してください',
styleColor: 'color: #f00'
},
watch: {
number(val){
if (!this.pattern.test(val) && val) {
this.isError = true;
} else {
this.isError = false;
}
}
}
})
{{errorMsg}}
filters
共通のテキストフォーマットを適用するための機能。コンポーネント内にオプションとして記述し特定のコンポーネント内でのみ使用可能なローカルフィルターと、コンポーネントの外に記述しすべてのコンポーネントから使用できるグローバルフィルターがある。加工したデータを返す処理はcomputedと同じだが、filtersの中ではthisが使えないのが特徴。Vue 3では廃止されていて、methodsやcomputedを使うよう推奨されている。
フィルターの使い方
{{対象のデータ | フィルター名 | フィルター名(引数,...)}}
<要素名 v-bind:属性名="対象のデータ | フィルター名">
フィルターは対象データの末尾を「|(パイプ)」で区切りフィルター名を記述することで呼び出し、複数繋げて使用する場合は左から順に実行される。
またフィルターは対象のデータを第一引数として受け取り処理を実行するが、第二、第三引数を持たせることも可能である。
<label>数値を入力 : <input v-model.number="price" type="number"></label>
<p>{{ price }}円(税抜)</p>
<p>{{ price | addTax(tax) }}円(税込)</p>
<p>{{ price | addTax | addComma }}円(税込)</p>
ローカルへの登録
new Vue({
el: '#App',
data: {
price: 10000,
tax: 8
},
filters: {
addTax: function (price, rate) {
const tax = (rate) ? rate : 10;
return Math.round(price + price * tax / 100);
}
}
})
{{ price }}円(税抜)
{{ price | addTax(tax) }}円(税込)
グローバルへの登録
//フィルター名が「addComma」の場合
Vue.filter('addComma', function (val) {
return val.toLocaleString();
});
{{ price }}円(税抜)
{{ price | addTax | addComma}}円(税込)
ライフサイクルフック
ライフサイクルフックとは、Vueのインスタンスが生成されてから破棄されるまでに、決まったタイミングで実行される関数のことで、この関数に処理を追記することができる。
new Vue({
el: '#App',
data: {
products: []
},
created: function () {
axios.get('外部データの格納場所').then(function (response) {
this.products = response.data;
}.bind(this)).catch(function (e) {
console.log(e);
});
}
})
| beforeCreate | インスタンスの生成時リアクティブデータが初期化される前に実行される。 このタイミングでインスタンスプロパティ利用し、dataオプションの内容やelオプションで紐づけた要素を例えばconsole.log($data)やconsole.log($el)と呼び出しても「undefined」になる。Vue 3のComposition APIでは使用しない。 |
|---|---|
| created | インスタンスの生成時リアクティブデータが初期化される後に実行される。 この時点でインスタンス自身を指すthisにアクセスできるので、dataオプションに予め定義したプロパティに外部から読み込んだjsonなどのデータを代入することができる。Vue 3のComposition APIでは使用しない。 |
| beforeMount | インスタンスがDOMに結びつく(マウント)される前。テンプレートがコンパイルがされ、インスタンスのプロパティがHTMLに描画される直前を指す。インスタンスにtemplateオプションがない場合、elオプションに定義された要素がテンプレートとされるので、この時点で$elの参照ができるが、レンダリングはされていない状態なのでDOMの操作はできない。 |
| mounted | インスタンスがDOMに結びつく(マウント)された後。最初の描画が完了し、DOMへの操作が可能な状態。 |
| beforeUpdate | リアクティブデータが変更され、DOMに反映される前(再描画される前)。再描画前のDOMからの要素の取得処理などを記述する。 |
| updated | リアクティブデータが変更され、DOMに反映された後(再描画された後)。 |
| beforeDestroy | インスタンスが破棄される直前。 createdやmountedなどでaddeventlistener関数を使ってイベントハンドラを登録している場合、このタイミングか次のdestoryedでremoveEventListener関数を使って必ず解除する。Vue 3では「beforeUnmount」に名称が変更。 |
| destoryed | Vueインスタンスが破棄された直後。Vue 3では「unmounted」に名称が変更。 |
Chromeデベロッパーツールを開いて確認。
↓クリックするとこのページで紐づけたインスタンスが破棄される。※インスタンスは「$destroy()」で意図的に破棄できる。