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

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

今回はMV**なフレームワークのひとつであるVue.jsを取り上げてみます。
さて、どこから手をつけたら良いものかと思案にくれた結果、序論や諸注意点をアレコレ展開する前にまずはビシバシと弄って、ヒジョーにシンプルなサンプルをいっぱい作って体にしみ込ませてみるのがイイんじゃないかと思いました。
ボリュームがあるので何回かに分け、今回はビューモデルを生成するパターンをまとめます。

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

[ViewModel生成編](http://qiita.com/setzz/items/3faf74b7141081731a75)
[ディレクティブ編](http://qiita.com/setzz/items/25041c1d46452de84aa6)
[インスタンスメンバ編]
(http://qiita.com/setzz/items/ebbfcc3565bcd27f344c)
[グローバルメソッド編]
(http://qiita.com/setzz/items/8f06f2fe6049173b17f9)
[フィルター編]
(http://qiita.com/setzz/items/e37c47d3f22e5738eb84)
[v-repeatネスト編]
(http://qiita.com/setzz/items/6f22ebd15bcc0afe1ab0)
[全体の目次はこちら](http://qiita.com/setzz/items/ad9a9c99a4fda041a563)

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

- IE8以下には非対応
- MVVMアーキテクチャにフォーカスしている
- MVVMとはモデル(M)とビュー(V)間のやり取りをビューモデル(VM)によって行うアーキテクチャらしい

# Vueインスタンスの生成

## Vueインスタンス

ビューモデルというのを作ってDOM要素にバインドするのが基本的な流れのようです。
サンプルコードはHTMLのbody要素におブチ込み頂けると動くかと思います。
ちょいちょいv-というディレクティブが入ってきますが次回やるので今回はさらっと流します。

## el,dataオプション

まずは基本のサンプルとしてel,dataオプションを使ってみます。
データバインディングを体で感じでみます。

|オプション|説明|
|---|---|
|el|セレクタを使ってバインド先のDOM要素を指定する|
|data|ビューモデルが保持するデータ群。|
```html:データバインディングして画面に表示してみる
<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.$el`や`vm.$data`でビューモデルの属性にアクセス出来る
- `vm.$data`や`vm.$methods`は`vm`でアクセスも可能
- ブラウザコンソールから`vm.bind1 = 100;`と叩けば画面の表示も遷移無しに変更される

今回、dataの中にはただのデータを突っ込んでいますが、モデルはこの中に定義するのもやり方としてあるようです。
また、疑問に思ったのでやってみたのですが１つのDOM要素に複数のビューモデルがバインドされた際には先にバインドしたビューモデルが勝つようです。

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

## methods,computedオプション

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

|オプション|説明|
|---|---|
|methods|ビューモデルのメソッドを保持するオブジェクト。methodsに限らないがビューモデル内で定義されたメソッド内でのthisはビューモデル自身を指す|
|computed|動的なプロパティを生成するメソッドを持つオブジェクト。methodsとの違いとして呼び出しに括弧を用いずプロパティであるかのようにアクセス出来る。またgetやsetの定義が出来る|

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

```html: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は括弧無しであたかもプロパティであるかのようにアクセス出来る

```html: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() はリアクティブな依存ではないため、次の算出プロパティは二度と更新されません:

```javaScript:キャッシュされる例
computed: {
  now: function () {
    return Date.now()
  }
}
```
対称的に、メソッド呼び出しは、再描画が起きると常に関数を実行します。

## paramAttributesオプション

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

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

- 属性の取得

```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が置換されます。)

```html:テンプレート
<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要素が破棄される

```html:テンプレートでリプレース
<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で属性の指定が出来る。

```html: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:生成される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にデータを定義。
 
```html:初期化時コールバック
<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!';`などと叩けば再度コールバックは実行される

```html:バインド時コールバック
<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要素のアクセスでは関数は実行されない。

```html: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オプション

```html:カスタムディレクティブ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オプション

```html:配列をソートするカスタムフィルタ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要素自体に使おうとすると警告が発生する

```html: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}}という記述も可能

```html:既存の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|ビューモデルに親子関係を持たせる。子は親のデータにアクセス可能|


```html:子から親のデータを引っ張ってバインド
<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編集時などに、同期的に値が書き換えられず、フォーカスを外れてからデータが更新される|

```html:フォーカスを外れたら更新(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>
```
===

今回は以上です。
結構手探り間がありますが、体で覚え中なので致し方ないかなと、とにかく前に進むのみ！
次回はディレクティブを体で覚えていきたいと思います。