Edited at

体で覚えるVue.js - ViewModel生成編 〜 JSおくのほそ道 #022

More than 1 year has passed since last update.

こんにちは、ほそ道です。

今回はMV**なフレームワークのひとつであるVue.jsを取り上げてみます。

さて、どこから手をつけたら良いものかと思案にくれた結果、序論や諸注意点をアレコレ展開する前にまずはビシバシと弄って、ヒジョーにシンプルなサンプルをいっぱい作って体にしみ込ませてみるのがイイんじゃないかと思いました。

ボリュームがあるので何回かに分け、今回はビューモデルを生成するパターンをまとめます。

「考えるな、感じろ」の精神でやった後にどう考え学習すべきかや、どう設計すべきかなどの私見は述べられたら良いなと思っております。

ViewModel生成編

ディレクティブ編

インスタンスメンバ編

グローバルメソッド編

フィルター編

v-repeatネスト編

全体の目次はこちら


ザックリとしたVue.js概念


  • IE8以下には非対応

  • MVVMアーキテクチャにフォーカスしている

  • MVVMとはモデル(M)とビュー(V)間のやり取りをビューモデル(VM)によって行うアーキテクチャらしい


Vueインスタンスの生成


Vueインスタンス

ビューモデルというのを作ってDOM要素にバインドするのが基本的な流れのようです。

サンプルコードはHTMLのbody要素におブチ込み頂けると動くかと思います。

ちょいちょいv-というディレクティブが入ってきますが次回やるので今回はさらっと流します。


el,dataオプション

まずは基本のサンプルとしてel,dataオプションを使ってみます。

データバインディングを体で感じでみます。

オプション
説明

el
セレクタを使ってバインド先のDOM要素を指定する

data
ビューモデルが保持するデータ群。


データバインディングして画面に表示してみる

<div id="sample">

<div v-text="bind1"></div>
<div v-model="bind2"></div>
<div>{{bind3}}</div>
</div>
<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
data: {
bind1: '111',
bind2: '222',
bind3: '333'
}
});
</script>


  • new Vue()でビューモデルを作成する

  • elでDOM要素を指定し、ビューモデルの適用範囲を指定する

  • ビューモデルのdataオブジェクトの属性はv-textやv-modelディレクティブによってバインドされる


  • vm.$elvm.$dataでビューモデルの属性にアクセス出来る


  • vm.$datavm.$methodsvmでアクセスも可能

  • ブラウザコンソールからvm.bind1 = 100;と叩けば画面の表示も遷移無しに変更される

今回、dataの中にはただのデータを突っ込んでいますが、モデルはこの中に定義するのもやり方としてあるようです。

また、疑問に思ったのでやってみたのですが1つのDOM要素に複数のビューモデルがバインドされた際には先にバインドしたビューモデルが勝つようです。

とりあえずビュー側(div要素)にv-textやv-modelディレクティブというのが出てきますが今回の対象外なので、あーなんかうまい事データバインドするのね、くらいの理解にとどめます。


methods,computedオプション

固定の値だけではなく、動的な値をデータバインドするときにはこんなオプションが使えるようです。

オプション
説明

methods
ビューモデルのメソッドを保持するオブジェクト。methodsに限らないがビューモデル内で定義されたメソッド内でのthisはビューモデル自身を指す

computed
動的なプロパティを生成するメソッドを持つオブジェクト。methodsとの違いとして呼び出しに括弧を用いずプロパティであるかのようにアクセス出来る。またgetやsetの定義が出来る


  • methodsはビューからは括弧付きで呼び出される


methodsを使ったデータアクセス

<div id="sample">

<div v-text="fullName()"></div>
</div>
<script src="js/vue.js"></script>
<script>
var user = new Vue({
el: '#sample',
data: {
firstName: 'Taro',
lastName: 'Yamada'
},
methods: {
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
});
</script>


  • computedは括弧無しであたかもプロパティであるかのようにアクセス出来る


computedを使ったデータアクセス

<div id="sample">

<div v-text="fullName"></div>
</div>
<script src="js/vue.js"></script>
<script>
var user = new Vue({
el: '#sample',
data: {
firstName: 'Taro',
lastName: 'Yamada'
},
computed: {
fullName: {
get: function() {
return this.firstName + ' ' + this.lastName;
}
}
}
});
</script>


methodsとcomputedの使い分け

最終的には、2つのアプローチは完全に同じ結果になります。


しかし、computedプロパティは依存関係にもとづきキャッシュされるという違いがあります。computedプロパティは、依存するものが更新されたときにだけ再評価されます。つまり、dataで定義された中身が変わらない限り、computedプロパティで定義された関数に何度アクセスしても、再び実行することなく以前計算された結果を即時に返します。

Date.now() はリアクティブな依存ではないため、次の算出プロパティは二度と更新されません:


キャッシュされる例

computed: {

now: function () {
return Date.now()
}
}

対称的に、メソッド呼び出しは、再描画が起きると常に関数を実行します。


paramAttributesオプション

el要素の属性を取得できます。

オプション
説明

paramAttributes
配列形式でHTML要素の属性名を列挙するとビューモデルのプロパティとして格納される


  • 属性の取得


el要素のstyle属性にアクセス

<div id="sample" style="font-size:30px">foo</div>

<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
paramAttributes: ['style']
});
console.log(vm.style); // font-size:30px
</script>


template, replaceオプション

テンプレートを使う事でHTMLの内容を書き換えます

オプション
説明

template
$elで指定した要素に挿入されるHTML文字列

replace
templateとセットで使う事で$el要素自体を置換するかをbool値で指定する


  • el要素の内側をテンプレートで置換(上記のサンプルでは元々のdiv要素は存在し続け、内側のfooが置換されます。)


テンプレート

<div id="sample">foo</div>

<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
template: '<div>bar</div>'
});
console.log(vm.style); // font-size:30px
</script>


  • replaceを使うと置換される事でel要素が破棄される


テンプレートでリプレース

<div id="sample">foo</div>

<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
template: '<div>bar</div>',
replace: true
});
</script>


tagName, id, className, attributesオプション

ここまではel要素はあらかじめ存在するものとして扱ってきましたが、生成し挿入する事も出来ます。

インスタンス生成時のelオプションを省略するとデフォルトでは空のdiv要素がel要素として生成されます。下記はそのカスタマイズオプションです。

オプション
説明

tagName
elを省略したときに自動生成される要素をdiv以外で指定出来る

id
$el要素のid属性で文字列で指定する

className
$el要素のclass属性で文字列で指定する

attributes
$el要素の属性でオブジェクトリテラルで指定する


  • elの指定を省略すると自動でdivタグが生成され、これはDOM要素としてJSで扱う事が出来る。この時にid,className,attributesで属性の指定が出来る。


elなDOM要素の生成・追加

<script src="js/vue.js"></script>

<script>
var vm = new Vue({
tagName: 'p',
id: 'foo',
className: 'bar',
attributes: {
style: 'font-size:30px'
},
template: 'display text'
});
document.body.appendChild(vm.$el);
</script>


生成されるHTML

<p id="foo" class="bar" style="font-size:30px">display text</p>


今回、あえてDOMメソッドのAppendChildを使ってますが、DOM要素の追加はvm$.appendTo(追加先DOM要素);などでやるのがVue.jsのお作法なようです。


イベントフックな関数定義オプション

ビューモデルに特定の状態が発生したときに実行されるコールバック関数の定義オプションです。

今回はcreatedとattachedを取り上げてみます。

オプション
説明

created
ビューモデルの初期化時、データバインド前に実行される関数。$dataに動的なプロパティを追加出来る。関数内でthis.$watchメソッドを呼び出すとメソッド引数として渡したコールバック関数がデータバインド時に実行される

ready
データバインドが完了した状態で実行される関数。$dataへのプロパティ追加は無視される

attached
$el要素がDOMツリーに追加されたときに呼ばれる関数

detatched
$el要素がDOMツリーから削除されたときに呼ばれる関数

beforeDestroy

vm.$destroy()関数が呼ばれビューモデルが破棄される前に呼ばれる関数

afterDestroy

vm.$destroy()関数が呼ばれビューモデルが破棄された後に呼ばれる関数


createdオプション


  • オブジェクト定義時に$dataにデータを定義。


初期化時コールバック

<div id="sample" v-text="value"></div>

<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
created: function() {
this.$data.value = 'Hello!';
}
});
</script>


  • $watchを使ってデータバインド時のコールバックを行う

  • ページ表示後にブラウザコンソールでvm.value = 'Bye!';などと叩けば再度コールバックは実行される


バインド時コールバック

<div id="sample" v-text="value"></div>

<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
data: {
value: 'Hello!'
},
created: function() {
this.$watch('value', function(newValue) {
console.log('reset data: ' + newValue);
});
}
});
</script>


attachedオプション


  • ページ表示後にブラウザコンソールでvm.$appendTo(document.body);を叩くとアラートが表示される。DOMメソッドのappendChildや$el要素のアクセスでは関数は実行されない。


el追加時コールバック

<script src="js/vue.js"></script>

<script>
var vm = new Vue({
attached: function() {
alert('ViewModel is attached!');
}
});
</script>


カスタムパーツ

オプション
説明

directives
カスタムディレクティブを提供する

filters
カスタムフィルタを提供する

components
部品化したDOM要素を提供する

partials
既存のDOM要素を部品化して再利用出来る

transitions
CSSトランジション(使い方がわからなかった。。)


directiveオプション


カスタムディレクティブv-consolelogの実装(コンソールにバインドした値を表示)

<div id="sample" v-consolelog="foo">Hello!</div>

<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
data: {
foo: 'CustomDirective',
},
directives: {
consolelog: function(value) {
console.log(value);
}
}
});
</script>


filtersオプション


配列をソートするカスタムフィルタmysortを実装

<div id="sample">

<span v-text="foo | mysort"></span>
</div>
<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
data: {
foo: [4, 2, 3, 1, 5]
},
filters: {
mysort: function(value) {
return value.sort()
}
}
});
</script>


componentsオプション

componentはelで指定した要素の内側には使用できるがel要素自体に使おうとすると警告が発生する


DOM要素の内側をコンポーネントで差し替え

<div id="sample">

<span v-component="foo"></span>
</div>
<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
components: {
foo: {
template: '<p>Component</p>'
}
}
});
</script>


partialsオプション


  • partialsにアクセスするにはv-partial以外にも{{> property}}という記述も可能


既存のDOM要素を部品化して再利用

<div id="sample">

<span v-partial="foo"></span>
<span>{{> foo}}</span>
</div>
<div id="other">OTHER</div>
<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
partials: {
foo: '#other'
}
});
</script>


parentオプション

オプション
説明

parent
ビューモデルに親子関係を持たせる。子は親のデータにアクセス可能


子から親のデータを引っ張ってバインド

<div id="sample">

<span v-text="foo"></span>
<span v-text="bar"></span>
</div>
<script src="js/vue.js"></script>
<script>
var parent= new Vue({
data: {
bar: 'BAR'
}
});
var child = new Vue({
el: '#sample',
parent: parent,
data: {
foo: 'FOO'
}
});
</script>


lazyオプション

オプション
説明

lazy
INPUT編集時などに、同期的に値が書き換えられず、フォーカスを外れてからデータが更新される


フォーカスを外れたら更新(lazyをfalseにもしてみると違いが一目瞭然)

<form id="sample" action="">

<div v-text="foo"></div>
<input type="text" v-model="foo">
</form>
<script src="js/vue.js"></script>
<script>
var vm = new Vue({
el: '#sample',
data: {
foo: 'defaultValue',
},
lazy: true
});
</script>

===

今回は以上です。

結構手探り間がありますが、体で覚え中なので致し方ないかなと、とにかく前に進むのみ!

次回はディレクティブを体で覚えていきたいと思います。