Vue.js 備忘録

簡単な HTML のデモを作るのに Vue.js を重宝している。「この値が変わるとこっちの値も変わる」ような連動する画面要素を作るのに便利。久しぶりに触ると完全に忘れていて困ったので [Introduction — Vue.js](https://vuejs.org/v2/guide/) のメモを残す。日本語訳もあり助かる。

宣言的にきれいに書くコツ

* v-on: イベントハンドラは使わない。
* v-model と computed を駆使する。

## 基本

```html
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
```

を含める。

```html
<div id="app">
    {{ message }}
</div>
<script>
 var app = new Vue({
     el: '#app',
     data: {
         message: 'Hello Vue!'
     }
 })
</script>
```

* 新しい `Vue` オブジェクトを作り、描画したい HTML 要素の ID を `el` で指定する。この例では Vue オブジェクトを app と名付ける。
* `data` の中に、HTML から参照するデータ変数の初期値を定義する。
  * `data` 内に定義したデータは不思議な事に app.message のようにして参照出来るようになる。
* HTML タグのテキスト要素内に `{{}}` でくくって `data` で定義した名前を書くと、HTML の中に描画される。

## v-bind: HTML 属性の操作

HTML テキスト要素ではなく、HTML 属性を動的に変更したい場合は v-bind:属性名 という属性を使う。以下の例では app.title が使われる。

```html
<div v-bind:title="title">Please hover</div>
```

## v-if, v-for: 条件表示とループ

条件による表示非表示の切り替えには `v-if`, ループには `v-for` を使う。

```html
<div>Condition:
    <span v-if="seen">Now you see me</span>
</div>
<ul>
    <li v-for="(place, index) in places" v-bind:key="place.id">
        {{index}}: {{ place.name }}
    </li>
</ul>
<script>
 var app = new Vue({
     el: '#app',
     data: {
         seen: true,
         places: [
             { id: 'tower1', name: '通天閣' },
             { id: 'tower2', name: 'スカイツリー' },
         ],
     }
 })
```

* `v-for` の中で `(place, index) in places` のようにすると、リストの要素とインデックスを取得出来る。
  * インデックスが不要な時は `place in places` で良い。
* `v-bind:key` で要素に一意の値を指定すると、再描画の最適化に使われる。
  * 再描画時 key の変更が無い要素を使い回す。
  * key の指定が無ければ、要素はそのまま内容を書き換える。(参照: [key](https://vuejs.org/v2/guide/list.html#key))

## v-on: イベント

v-on:イベント名 属性でイベントハンドラを設定する。イベントハンドラ内で data 内の変数を変更すると画面は自動的に再描画される。

```html
<div v-on:click="onClick">Event: {{ time }}</div>
<script>
 var app = new Vue({
     el: '#app',
     data: {
         time: 'Click me',
     },
     methods: {
         onClick: function(e) {
             this.time = new Date();
         }
     },
 })

```

## v-model: フォーム入力

v-model で HTML フォームと Vue 変数を結びつける。これを React.js でやると果てしなく面倒臭い。フォームに入力された内容を変数にセットするだけで一見単純に見えるが、value, checked, selected 等フォームの違いによる属性の差を吸収してくれる。

```html
<div>Template: {{ message }}</div>
<input v-model="message"/>
<script>
 var app = new Vue({
     el: '#app',
     data: {
         message: 'Dynamic message',
     },
 })
</script>

```

詳しくは [Form Input Bindings](https://vuejs.org/v2/guide/forms.html)

## 省略記法

検索し辛い省略記法 [Shorthands](https://vuejs.org/v2/guide/syntax.html#Shorthands)

* HTML 属性を操作する v-bind は省略出来る。
  * 省略前: `<a v-bind:href="url"> ... </a>`
  * 省略後:`<a :href="url"> ... </a>`
* @ はイベントハンドラの v-on の意味
  * 省略前: `<a v-on:click="doSomething"> ... </a>`
  * 省略後: `<a @click="doSomething"> ... </a>`


## computed: 自動計算 [Computed Properties](https://vuejs.org/v2/guide/computed.html#Computed-Properties)

ここが Vue.js の一番おもしろい所。まるで Excel のように自動計算出来ます。以下の例では、角度の数値を度数法とラジアンを変換します。度数法を入れるとラジアンが変化し、ラジアンを入れると度数法が変化します。

```html
<div>Degree: <input v-model="degree"/></div>
<div>Radian: <input v-model="radian"/></div>
<script>
 var app = new Vue({
     el: '#app',
     data: {
         degree: 90,
     },
     computed: {
         radian: {
             get: function() {
                 return Math.PI * 2 * this.degree / 360
             },
             set: function(newValue) {
                 this.degree = 360 * newValue / (Math.PI * 2) ;
             }
         }
     }
 })
</script>
```

この例では、プロパティの読み書き両方に対応するために [setter 構文](https://developer.mozilla.org/ja/docs/JavaScript/Reference/Operators/set) を使っているが、読みだけで良い場合は普通の関数でもよい。

```
Degree: 90
Radian: 1.5707963267948966
```

## watch: データの更新を受け取る [Watchers](https://vuejs.org/v2/guide/computed.html#Watchers)

あるデータの変更によって表示を変えたい時はだいたい computed を使えば間に合う。ただ、ネットワークアクセス等で非同期に変更した場合は watch の方が良いらしい(computed 内で非同期処理をするとどうなるか調査中)

```html
<select v-model="yesno">
    <option value="yes">Yes</option>
    <option value="no">No</option>
</select>
<img v-bind:src="yesnoImg"/>
<script>
 var app = new Vue({
     el: '#app',
     data: {
         yesno: "yes",
         yesnoImg: "",
     },
     created: function() {
         this.yesno = "no";
     },
     watch: {
         yesno: async function(newValue, oldValue) {
             this.yesnoImg = "";
             const response = await fetch(`https://yesno.wtf/api?force=${this.yesno}`);
             const json = await response.json();
             this.yesnoImg = json.image;
         }
     },
     
 })
</script>
```

この例では、yes または no を選択するとそれに応じた画像を出力する。また `created` を使って起動時にも no の画像を出すようにする。

created などのすべての Vue オブジェクト作成時のプロパティは [API — Vue.js](https://vuejs.org/v2/api/) に記載がある。

コードサンプル: https://gist.github.com/propella/97af35302e9674475f0855de553c44ad