【Vue.js - 2】データの登録と更新

## 1. リアクティブデータの定義

リアクティブデータ…反応のできるデータ

```js:Vue.js
//オプション外でデータを定義
var state = { count: 0 }
//Vueインスタンス
var app = new Vue (
  el : '#app',
  data : {
    message: 'Vue.js!',
    state: state,
    //オプション外で定義されたデータでもリアクティブデータに変換
    //内容が決まってない場合のでも、初期値や空データにしておく
    //（なるべく、後から代入されるデータと同じ型で定義する）
    visitCount: 0,
    hideCompletedTodos: false,
    todos: [],
    error: null
  }
);
state.count++
```

## 2. テキストのデータバインディング

Mustache（マスタッシュ）で定義

```html:html
<!-- テキストを表示 -->
<p>{{ text }}</p>

<!-- オブジェクトのプロパティを表示 -->
<p>{{ message.value }}</p>

<!-- 文字列の長さを表示 -->
<p>{{ message.value.length }}</p>

<!-- 配列を表示 -->
<p>{{ list[2] }}</p>

<!-- プロパティを組み合わせて使用 -->
<p>{{ list[num] }}</p>
```
```js:Vue.js
new Vue({
  el: '#app',
  data: {
    //テキストデータ
    text: 'Vue.js!',
    //オブジェクトデータ
    message: {
      value: 'Hello Vue.js!'
    },
    //配列データ
    list: ['りんご', 'ばなな', 'いちご'],
    //別のデータを使用してlistから取り出す要素を動的に 4 で使用
    num: 1
  }
})
```

MustacheにはJSの式は使えるが、文はエラーに（イコールの右側に記述できるのが式）

```html:html
<!-- ok -->
<p>{{ 1 + 1 }}</p>
<!-- error -->
<p>{{ var foo = message }}</p>
```

## 3. 属性のデータバインディング

v-bindディレクティブ

```html:html
<!-- error -->
<input type="text" value="{{ message }}">
<!-- b-vindで記述 -->
<input type="text" v-bind:value="message">
<!-- 省略記法 -->
<input type="text" :value="message">
```
v-bindの修飾子

| 修飾子 | 説明 |
|:-- |:-- |
| .prop &nbsp;&nbsp;&nbsp; | 属性の代わりにDOMプロパティとしてバインド  |
| .camel&nbsp;  | ケバブケースの属性名をキャメルケースに変換する  |
| .sync | 双方向パインディングをお香なう  |

```html:html
<div v-bind:text-content.prop="message"></div>
<div v-bind:scroll-top.prop="scroll"></div>
```

```js:js
new Vue({
  el: '#app',
  data: {
    message: 'Vue.js!',
    scroll: 0
  },
  mounted: function() {
    this.scroll = 100//要素のスクロール量を操作
  }
});
```

## 4. データの更新

```html:html
<div id="app">
   <p>{{ count }}回クリックしたよ！ </p>
   <button v-on:click="increment">カウントを増やす</button>
 </div>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    count: 0
  },
  methods: {
    increment: function () {
      this.count += 1 //処理は再代入するだけでOK！
    }
  }
})
```

## 5. クラスとスタイルのデータバインディング
 
```html:html
<button v-on:click="isActive=!isActive">
    isActiveを切り替える
</button>
<p v-bind:class="{ child: isChild, 'is-active': isActive }" class="item">
    childとis-activeというclassが付与される動的なクラス
</p>
<p v-bind:style="{ color: textColor, backgroundColor: bgColor }" class="item">
    動的なスタイル
</p>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    isChild: true,
    isActive: true,
    textColor: 'red',
    bgColor: 'lightgray'
  }
})
```

クラスの条件の三項演算子を使う

```html:html
<p v-bind:class="[isActive ? 'active' : 'normal', otherClass]">Text</p>
<!-- 複数のクラスやスタイルは、dataオプションにオブジェクトを定義してから渡すと見通しが良くなる -->
<p v-bind:class="classObject">Text</p>
<p v-bind:style="classObject">Text</p>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    classObject: {
      child: true,
      'is-active': false
    },
    styleObject: {
      color: 'red',
      backgroundColor: 'lightgray'
    }
  }
})
```

## 6. 複数の属性の一括データバインディング

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    item: {//複数の属性をオブジェクトにまとめる
      id: 1,
      src: 'item.jpg',
      alt: '商品サムネイル',
      width: 200,
      height: 200,
    }
  }
})
```

```html:html
<!-- これだと冗長 -->
<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を併用すれば、特的の要素のみ変更を加えられる -->
<img v-bind="item" v-bind:id="'thumb-'+ item.id">
```

## 7. SVGのデータバインディング

```html:html
<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>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    radius: 50
}
```

## 8. 条件分岐

```html:html
<div v-if="ok">v-if条件による描写</div>
<div v-show="ok">v-show条件による表示</div>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    ok: false
  }
})
```

条件を満たさなかった場合：
**v-if**
要素がDOMレベルで削除・全ての監視は解除
コンポーネントならインスタンスは破棄
次に描写される時には状態は初期化
**v-show**
単純にdisplay:none;を付与

使い分け：
**v-if**
内側に directiveやコンポーネントを多様している
特定のデータを持ってないとエラーが起きる
**v-show**
内側にディレクティブやコンポーネントが少ない、切り替えの頻度が高い

グループ化（templateタグ）

```html:html
<template v-if="ok">
 <header>タイトル</header>
 <div>コンテンツ</div>
</template>
```

複数条件（v-else-if,v-else）はそれぞれにユニークなkeyを設定して、予期せぬ動作を回避

```html:html
<div v-if="type === 'A'" key="ユニークなkey名"></div>
<div v-else-if="type === 'B'"  key="ユニークなkey名"></div>
<div v-else key="ユニークなkey名"></div>
```


## 9. リストデータの表示と更新

リストの表示

```html:html
<li v-for="item in list"></li>
<li v-for="(item, index) in list"></li>
<li v-for="(item, key, index) in list"></li>
```

キーの役割（ユニークなIDであるidプロパティをキーとしてデータバインディングする）

```html:html
<li v-for="item in list"　v-bind:key="item.id"></li>
 ```

繰り返し+条件分岐

```html:html
<div id="app">
  <ul>
    <li v-for="item in 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>
</div>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    list: [
      { id: 1, name: 'スライム', hp: 100 },
      { id: 2, name: 'ゴブリン', hp: 200 },
      { id: 3, name: 'ドラゴン', hp: 500 }
    ]
  }
})
```

リストの更新

```html:html
<div id="app">
 <!-- このフォームの入力値を新しいモンスターの名前に使う -->
 名前
 <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>
</div>
```

```js:Vue.js
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 > b.id ? a : b.id
      }, 0)
      //新しいモンスターをリストに追加
      this.list.push({
        id: max + 1, //現在の最大のIDに+1してユニークなIDを作成
        name: this.name, //現在のフォームの入力値
        hp: 500
      })
    }
  }
})
```

リストを削除

```html:html
<div id="app">
 <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>
</div>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    list: [
      { id: 1, name: 'スライム', hp: 100 },
      { id: 2, name: 'ゴブリン', hp: 200 },
      { id: 3, name: 'ドラゴン', hp: 500 }
    ]
  },
  methods: {
    //要素を削除ボタンをクリックしたときのハンドラ
    doRemove: function (index) {
      //受け取ったインデックスの位置から1個要素を削除
      this.list.splice(index, 1)
    }
  }
})
```

リストの置き換え

```js:Vue.js
//this.$set(更新するデータ, インデックスorキー, 新しい値)
this.$set(this.list, 0, { id: 1, name: 'キングスライム', hp: 500})
```

プロパティの値を追加

```html:html
<div id="app">
  <ul>
    <li v-for="(item, index) in list" v-bind:key="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>
</div>
```

```js:Vue.js
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を減らす
    }
  }
})
```

リスト自体を置き換える（filter）

```js:Vue.js
this.list = this.list.filter( function(el) {
  return el.hp >= 100
})
```

オプションにデータを持たないv-for

```html:html
<span v-for="item in 15">{{ item }}</span>
<span v-for="item in [1, 5, 10, 15]">{{ item }}</span>
<!-- 文字列に対するv-forは、1文字ずつ配列になる -->
<span v-for="item in text">{{ item }}</span>
```

外部からデータを取得

```json:list.json
[
  { "id": 1, "name": "スライム", "hp": 100 },
  { "id": 2, "name": "ゴブリン", "hp": 200 },
  { "id": 3, "name": "ドラゴン", "hp": 500 }
]
```

```html:html
<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>
```

```js:Vue.js
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)
    })
  }
})
```

## 10. $elや$refsは一時的な変更！

ルートやコンポーネント要素を囲ってるルート要素は、
インスタンスプロパティの**\$el**を使って参照

```js:Vue.js
new Vue({
  mounted: function(){
    console.log(this.$el)  //-><div id="app"></div>
  }
})
```

ルート以外の要素は**$refs**を使って参照

```html:html
<div id="app">
  <button v-on:click="handleClick">カウントアップ</button>
  <button v-on:click="show=!show">表示/非表示</button>
  <span ref="count" v-if="show">0</span><!-- 対象となる要素にref属性で名前をつけておく -->
</div>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    show: true
  },
  methods: {
    handleClick() {
      var count = this.$refs.count
      if (count) {
        count.innerText = parseInt(count.innerText, 10) + 1
      }
    }
  }
})
```

## 11. テンプレート制御ディレクティブ

**v-pre**（テンプレートのコンパイルをスキップする）
サーバーサイドレンダリング時のXSS対策に用いる、操作をする予定のない長いHTML文章を記述している部分に使用する

```html:html
 <!-- Hello {{message}}がそのまま表示する -->
 <a v-bind:href="url" v-pre>Hello {{ message }}</a> 
```

**v-once**（一度だけバインディングを行う）

```html:html
 <!-- 1回だけmessageの内容を表示して以降、リアクティブを解除 -->
 <a v-bind:href="url" v-once>Hello {{ message }}</a>
```

**v-text**（Mustacheの代わりにテキストコンテンツを描写）

```html:html
<span v-text="message"></span>
```

**v-html**（HTMLタグをそのまま描画する）
APIから取得したHTML文章を描写する時に使用（信頼できるコンテンツのみ使用）

```html:html
<span v-html="message"></span>
```

**v-cloak**（インスタンスの準備が終わると取り除かれる）
インスタンス作成までの間、Mustacheなどのコンパイル前のテンプレートが画面に表示されてしまうことを防ぐ

```html:html
<div id="app" v-cloak>
  {{ message }}
</div>
```

```css:css
[v-cloak] { display: none; }
```

**v-cloak2**
画面の読み込み時には#app要素を隠し、
インスタンスが作成されるとv-cloak属性が外れ、フェードインしながら表示させる

```css:css
 @keyframes cloak-in {
   0% {
     opacity: 0;
   }
 }
 #app {
   animation: cloak-in 1s;
 }
 #app[v-cloak] {
   opacity: 0;
 }
```