以下の記事は2019/2/7 コワーキングスペース秋葉原Weeybleで行われる輪読会
[秋葉原] 基礎から学ぶVue.js輪読会 ch2 データの登録と更新(初心者歓迎!)の読書メモです。以下、書籍のch2 「データの登録と更新」の書籍メモです。
目次
- CHAPTER2 データの登録と更新
- 07 基本のデータバインディング
- 08 テキストと属性のデータバインディング
- 09 テンプレートにおける条件分岐
- 10 リストデータの表示と更新
- 11 DOMを直接参照する$elと$refs
- 12 テンプレート制御ディレクティブ
はじめに
本書籍のサポートサイトです。本輪読会ではこちらのサンプルコードを使ってVue.jsの動きを確認していきます。
基礎から学ぶVue.js
基礎から学ぶVue.js-コード&動作デモ-CH2 データの登録と更新
CHAPTER2 データの登録と更新
07 基本のデータバインディング
「bind」ー関連付け、割り当て、縛る、紐付ける
*「データバインディング」を行うには、テンプレートで使用するすべてのデータは「リアクティブデータ」
として定義する必要がある。
- リアクティブデータとは、Vue.jsによって取得したとき(get)と代入したとき(set)のフック処理が登録された、
反応できるデータのこと
。
リアクティブなデータの定義
- コンポーネントの
data
オプションに文字列やオブジェクトなどのデータを定義 - インスタンス作成時(どういうとき?)にリアクティブなデータに変換する
var app = new Vue({
el: '#app',
data: { // dataオプションに文字やオブジェクトデータを定義
message: 'Vue.js!' // messageは変化を検地できるようになる
} // (リアクティブなデータとして定義)
})
-
data
オプションの直下のプロパティは後から追加できないため、内容が決まっていない場合でも初期値や空データとして定義しておく必要がある
data: {
newTodoText: ' ',
visitCount: 0,
hideCompletedTodos: false,
todos: [],
error: null
}
08 テキストと属性のデータバインディング
テキストのデータバインディング
「Mustache記法 = {{ message }
Mustacheとはロジックレスのテンプレートエンジンで、その最大のウリはMustacheの記法を覚えると様々な言語で使えるから便利だよ―! ということみたいです。 特定の言語に依存しない記法になっているのは素晴らしいコンセプトで心惹かれます。
Bashでテンプレートエンジン{{Mustache}}を使ってみる
<div id="app">
<p>Hello {{ message }}</p>
</div>
<div id="app">
<p>Hello Vue.js!</p> //中括弧で記述した部分だけが置き換わる
</div>
◆オブジェクトや配列要素の表示
データバインディング(DataBinding=データ結合・割り当て・紐付け)では、
ルート(?)に定義したプロパティだけではなく、ネスト(nest=入れ子)されているオブジェクトのプロパティや、配列の要素も使用することもできます。
// どこがネスト(入れ子)なのか?
new Vue({
el: '#app',
data: {
// オブジェクトデータ
message: {
value: 'Hello Vue.js!'
},
// 配列データ
list: ['りんご','ばなな','いちご'] ,
// 別のデータを使用してlistから取り出す要素を動的に
num: 1
}
})
<p>{{ message.value }}</p>
<p>{{ message.value.length }}</p> // どういう意味?
<p>{{ list[2] }}</p>
<p>{{ list[num] }}</p> <!-- プロパティを組み合わせて使用 -->
<p>Hello Vue.js</p>
<p>13</p>
<p>いちご</p>
<p>ばなな</p>
◆式と文の違いに注意しよう!
※CHAPTER4「データの監視と加工」で解説するそうなので、割愛。
{{ 1+1 }}
// 次のコードは"式ではなく文"になるため使用できない(?)
{{ var foo = message }}
属性のデータバインディング(v-bind)
「Mustache={{ message }}」はテキストコンテンツ特有の記法のため、属性(value)に使用することはできない。属性(value)を展開するには、v-bind
ディレクティブを使用する
<input type="text" value="{{ message }}">
<!-- Error compiling template -->
```html:属性へのバインドにはv-bind
ディレクティブを使用
// 省略せずに書いた場合
// 省略して書いた場合(「v-bind:」→「:」)
```html:実際の描画
<input type="text" value="Vue.js!">
v-bindの修飾子(オプション)
修飾子 | 作用 |
---|---|
.prop | 属性の代わりにDOMプロパティとしてバインドする |
.camel | ケバブケースの属性名をキャメルケースに変換する |
.sync | 双方向バインディングを行う(CHAPTER5で解説) |
```html:.prop
修飾子は、DOMのプロパティと直接バインド(???)
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
scroll:0
},
mounted: function() {
this.scroll = 100 // 要素のスクロール量を操作(???)
}
})
データの更新
data
オプション直下のデータは、たとえばmessage
やcount
といった名前を持つプロパティになっているため、これらはすべてリアクティブです。なぜプロパティであることが関係するかについては、この後の「リストデータ」の表示と「更新」のセクション(70ページ)で説明します。
◆クリックでカウンターを増やそう(v-on
ディレクティブ)
v-on
ディレクティブを使用して、ボタンをクリックされるとincrement
という
名前のメソッドが呼び出されるようにします
```html:ボタンをクリックされるとincrement
メソッドを呼び出す
{{ count }}回クリックしたよ!
カウントを増やす```javascript:count
プロパティを「1」増やす処理
new Vue({
el: '#app',
data: {
count: 0
},
methods: {
// ボタンをクリックしたときのハンドラ
increment: function() {
this.count += 1 // 処理は再代入するだけでOK !
}
}
})
## コラム(thisは今、何を指している?)→パス
## ◆クラスとスタイルのデータバインディング(???)
```html
<p v-bind:class="{ child: isChild, 'is-active': isActive }">Text</p>
<p v-bind:style="{ color: textColor, backgroundColor: bgColor }">Text</p>
new Vue({
el: '#app',
data: {
isChild: true,
isActive: true,
textColor: 'red',
bgColor: 'lightgray'
}
})
<p class="child is-active">Text</p>
<p style="color: red; background-color: lightgray">Text</p>
複数の属性のデータバインディング
V-bind
ディレクティブの引数部分を省略してオブジェクトを渡すと、
一括バインドができる
<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"> // `v-bind`ディレクティブの引数部分を省略
◆SVGのデータバインディング
- SVGとは?
Scalable Vector Graphics(スケーラブル・ベクター・グラフィックス)の略で、一種の画像フォーマットになります。JPEGやPNGと言ったようなWebでよく見かけられる画像との違いは、PEGやPNGがビットマップデータなのに対し、SSVGはXMLをベースにした二次元ベクターデータであることです。このベクターデータとは「画像を、点の座標とそれを結ぶ線(ベクター・ベクトル)などの数値データをもとにして演算によって再現する方式」であり、このデータ形式で作られた画像は「拡大・縮小しても画質が損なわれない」といった特徴を持っています<「今こそ取り込むSVG
とは | 株式会社オプティマイザー)
- SVGのDOMとは?
<div id="app">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="75" v-bind:r="radius" fill="lightpink" />
</svg>
<input type="range" min="0" max="100" v-model="radius">
</div>
new Vue({
el: '#app',
data: {
radius: 50
}
})
09 テンプレートにおける条件分岐(v-if
とv-show
ディレクティブ)
- v-if条件による描画(削除、初期化される)
条件を満たさない場合、要素はDOMレベルで削除されすべての監視は解除されます。コンポーネントならインスタンスは破壊され、次に描画されるときには状態は初期化されています。
- v-show条件による表示
条件を満たさなかった場合、単純にdisplay:none;スタイルを付与します。目に見えなくてても、内側は常にリアクティブで監視されていることに注意しましょう。
<div v-if="ok">v-if条件による描画</div>
<div v-show="ok">v-show条件による表示</div>
new Vue ({
el: '#app',
data: {
ok: false
}
})
*** タグによるv-ifグループ化***
<template v-if="ok">
<header>タイトル</header>
<div>コンテンツ</div>
</template>
*** v-eles-ifおよびv-elseによるグループ化***
<div v-if="type === 'A' ">
typeはA
</div>
<div v-else-if="type === 'B' ">
typeはB
</div>
<div v-else>
すべての条件を満たさなかった
</div>
10 リストデータの表示と更新
要素を繰り返し描画する(v-forディレクティブ)
<li v-for="各要素を代入する変数名 in 繰り返したい配列やオブジェクト">
<div id="app">
<ul>
<li v-for="item in list" v-bind:key="item.id">
ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
</li>
</ul>
</div>
new Vue({
el: '#app',
data: {
list: [
{ id: 1, name: 'スライム', hp: 100}.
{ id: 2, name: 'ゴブリン', hp: 200}.
{ id: 3, name: 'ドラゴン', hp: 500}.
<div id="app">
<ul>
<li>ID.1 スライム HP.100</li>
<li>ID.2 ゴブリン HP.200</li>
<li>ID.3 ドラゴン HP.500</li>
</ul>
</div>
インデックス(?)とオブジェクトキー(?)の使用
// 変数部分を括弧で囲み、配列インデックスを任意に受け取れます。
<li v-for="(item,index) in list">...</li>
// オブジェクトなら「値」「キー」「インデックス」の順で任意に受け取れます。
<li v-for="(item,key,index) in list">...</li>
繰り返し描画しながら、さまざまな条件を適用する
- 条件を使ったクラスの操作
// `hp`プロパティが「300」より大きいモンスターのみ
// `.tuyoi`クラスを付与して、さらに「つよい!」の文字を描画
<ul>
<li v-for="item-list"
v-bind:key="item.id"
v-bind:class="{tuyoi: item.hp > 300 }">
ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
<span v-if="item.hp" > 300">つよい!</span>
</li>
</ul>
<ul>
<li>ID.1 スライム HP.100</li>
<li>ID.2 ゴブリン HP.200</li>
<li class="tuyoi">ID.3 ドラゴン HP.500<span>つよい!</span></li>
</ul>
描画と表示の条件(要素に直接、v-ifを付与)
<ul>
<li v-for="item in list" v-bind:key="item.id" v-if="item.hp" < 300">
ID.{{ item.ID }} {{ item.name }} HP.{{ item.hp }}
</li>
</ul>
<ul>
<li>ID.1 スライム HP.100</li>
<li>ID.2 ゴブリン HP.200</li>
</ul>
リストの更新
- インデックス数値を使った配列要素の更新
- 後から追加されたプロパティの更新
this.list = [] // これはプロパティの更新
this.list[0].name = 'NEW' // これはプロパティの更新
this.list[0] = 'NEW' // これは配列要素の更新なのでNG!
リストに要素を追加しよう
// リストに新しい要素を追加するには、配列メソッドの`push`や`unshift`を使用
this.list.push(新しい要素)
<!-- このフォームの入力値を新しいモンスターの名前に使う -->
名前<input v-model="name">
<button v-on:click="doAdd">モンスターを追加</button>
<ul>
<li v-for="item in list" v-bind:key="item.id">
ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
</li>
</ul>
new Vue({
el: '#app',
data: {
name: 'キマイラ',
list: [
{ id: 1, name: 'スライム', hp: 100},
{ id: 2, name: 'ゴブリン', hp: 200},
{ id: 3, name: 'ドラゴン', hp: 500}
]
},
methods: {
// 追加ボタンをクリックしたときのハンドラ
doAdd: function() {
// リスト内で1番大きいIDを取得
var max = this.list.reduce(function(a,b) {
return a.id > b.id ? a.id : b.id
}
リスト要素を削除しよう(配列メソッドsplice)
<ul>
<li v-for="(item,index) in list" v-bind:key="item.id">
ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
<!-- 削除ボタンをv-for内に作成 -->
<button v-on:click="doRemove(index)">モンスターを削除</button>
</li>
</ul>
<button v-on:click="doRemove(index)">モンスターを削除</button>
new Vue({
// ...
methods: {
// 削除ボタンをクリックしたときのハンドラ
doRemove: function(index) {
// 受け取ったインデックスの位置から1個要素を削除
this.list.splice(index,1)
}
}
})
リスト要素プロパティを更新しよう
更新対象がプロパティなら、
this.list[0] = { id: 1, name: 'キングスライム', hp: 500}
this.list[0]
this.$set(更新するデータ,インデックス or キー, 新しい値)
// this.set()を使って書き換えると次のように
this.$set(this.list,0,{id: 1, name: 'キングスライム', hp:500})
◆プロパティを追加(this.$setメソッド)
new Vue({
el: '#app',
data: {
list: [
{ id:1, name: 'スライム', hp: 100 },
{ id:2, name: 'ゴブリン', hp: 200 },
{ id:3, name: 'ドラゴン', hp: 500 },
]
},
created: function() {
// すべての要素にactiveプロパティを追加したい
this.list.forEach(function(item) {
this.$set(item, 'active', false)
// 「item.active = false」ではリアクティブにならない
}, this)
}
})
◆リスト要素プロパティを更新しよう
<ul>
<li v-for"(item, index) in list" v-bind:lkey="item.id" v-if="item.hp">
ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
<span v-if="item.hp <50">瀕死!</span>
<!-- ボタンはv-for内に作成 -->
<button v-on:click="doAttack(index)">攻撃する</button>
</li>
</ul>
new Vue({
el: '#app',
data: {
list: [
{ id: 1, name: 'スライム', hp: 100 },
{ id: 2, name: 'ゴブリン', hp: 200 },
{ id: 3, name: 'ドラゴン', hp: 500 }
]
},
methods: {
// ボタンのクリックイベントのハンドラ
doAttack: function(index) {
this.list[index].hp -= 10 // HPを減らす
}
}
})
◆リスト自体を置き換えよう
// listはプロパティなので更新はリアクティブです!
this.list = this.list.filter(function(el) {
return el.hp >= 100
})
ユニークキーを持たない配列
<select>
<option v-for="item in list">{{ item }}</option>
</select>
data: {
list: ['スライム','ゴブリン','ドラゴン']
}
オプションにデータを持たないv-for
//<span>で囲まれた「1~15」の数字を描画
<span v-for="item in 15">{{ item }}</span>
<span v-for="item in [1,5,10,15]">{{ item }}</span>
<span v-for="item in [1, 5, 10, 15]">{{ item }}</span>
文字列に対するv-for
// 文字列にv-forを使用すると、1文字ずつ別々の要素として描画
<span v-for="item in text">{{ item }}</span>
new Vue({
el: '#app',
data: {
text: 'Vue'
}
})
<span>V</span>
<span>u</span>
<span>e</span>
外部からデータを取得する
[
{ "id":1, "name": "スライム", "hp": 100 },
{ "id":2, "name": "ゴブリン", "hp": 200 },
{ "id":3, "name": "ドラゴン", "hp": 500 }
]
<div id="app">
<ul>
<li v-for="(item, index) in list" v-bind:key="item.id">
ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
</li>
</ul>
</div>
new Vue ({
el: '#app',
data: {
// あらかじめ空リストを用意しておく
list: []
},
created: function() {
axios.get('list.json').then(function(response)) {
// 取得完了したらlistリストに代入
this.list = response.data
}.bind(this)).catch(function(e) {
console.error(e)
})
}
})
11 DOMを直接参照する$elと$refs(理解度=いまひとつ)
データバインディングを利用することでDOMの更新は効率化されますが、リアルなDOMへのアクセスが必要になるケースもあります。たとえば、要素の画面上の位置や高さはDOMでなければわかりません。
DOMにアクセスするためにはインスタンスプロパティ$elと$refsを使用します。この2つのプロパティは、DOMを参照するため、ライフサイクルのmounted
以降でなければ使用できません。
$elの使い方
ルートやコンポーネントのテンプレートを囲んでいるルート要素は、インスタンスプロパティの$el
を使って参照できます。
new Vue({
el: '#app',
mounted: function() {
console.log(this.$el) // -> <div id="app"></div>
}
})
$refsの使い方
<div id="app">
<p ref="hello">Hello</p>
<!-- p要素にhelloという名前を付けた -->
</div>
new Vue({
el: '#app',
mounted: function() {
console.log(this.$refs.hello) // これはp要素のDOM!
}
})
$elや$refsは一時的な変更!
<div id="app">
<button v-on:click="handleClick">カウントアップ</button>
<button v-on:click="show=!show">表示/非表示</button>
<span ref="count" v-if="show">0</span>
new Vue({
el: '#app',
data: {
show: true
},
methods.: {
handleClick() {
var count = this.$refs.count
if (count) {
count.innerText = parseint(Count.innerText,10)
}
}
}
})
12 テンプレート制御ディレクティブ
ディレクティブ | 作用 |
---|---|
v-pre | テンプレートのコンパイルをスキップする |
v-once | 一度だけバインディングを行う |
v-text | Mustacheの代わりにテキストコンテンツを描画 |
v-html | HTMLタグをそのまま描画する |
v-cloak | インスタンスの準備が終わると取り除かれる |
v-pre
v-preディレクティブは、子コンポーネントを含む内側のHTMLのコンパイルをスキッフして性的なコンテンツとして扱います
<a v-bind:href="url" v-pre>
Hello {{ message }}
</a>
// Mustacheやディレクティブがそのまま表示される
<a v-bind:href="url">Hello {{ message }}</a>
サーバーサイドレンダリング時のXSS対策に用いたり、
操作をする予定のない長いHTML文書を記述している部分に使用すると、
パフォーマンスの向上につながります
v-once
v-onceディレクティブには、初回だけテンプレートを評価しそれ以降は静的なコンテンツとして扱います。
<a v-bind:href="url" v-once>
Hello {{ message }}
</a>
// 1回だけmessageの内容を表示して以降リアクティブを解除
<a href="https//jp.vuejs.org/">Hello.js!</a>
v-text
要素内のテキストコンテンツが単一のMustacheのみで構成される場合、代わりに
v-text
ディレクティブを使って同じようにテキストコンテンツをバインドできます。
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})
<span v-text="message"></span>
<span>Hello Vue.js</span>
v-html
Mustacheを使った描画では、XSS対策のため、必ずHTMLエンティティ化されます。HTMLタグをそのまま表示させたい場合は
v-html
ディレクティブを使用します。
new Vue({
el: '#app',
data: {
message: 'Hello <strong>Vue.js!</strong>'
}
})
<span v-html="message"></span>
<span>Hello <strong>Vue.JS!</strong></span>
v-clock
<div id="app" v-cloak>
{{ message }}
</div>
[v-cloak] { display: none; }
また、次のようなスタイルを定義をすると、画面の読み込み時には
#app
要素を隠して、インスタンスが作成されるとv-cloak
属性が外れフェードインしながら表示します。
@keyframes cloak-in {
0% { pacity: 0;}
}
#app {
animation: cloak-in 1s;
}
#app[v-cloak] {
opacity: 0;
}