Vue.jsの基礎

# Vue.jsの基本的な使い方

- Reactは単方向データバインディングで一方通行なのに対し、Vueは双方向データバインディングで双方向にデータを流す仕組みになっている。
    - 双方向データバインディングの場合は`js`の中身が変わったらすぐに反映される
    - 単方向データバインディングの場合は`render`などの描画の処理が単方向であるため即時反映とはならない

# 環境構築　---未完成
- `vue.esn.js`というtemplate機能のないランタイム限定ビルドがあるので、これも必要に応じてnpmでインストールする

# 基本的な書き方
- `new Vue`でインスタンス生成
- `el`でセレクターを書き込み、スコープを指定する
- `data`プロパティはオブジェクトの形で自分が使いたいプロパティを定義する
    - データオブジェクトの中の値を出し入れするための入れ物のようなもの
- `data`で定義したプロパティは`html`で使うことができる


## ファイルのベースづくり

```javascript:app.js
import Vue from 'vue'

new Vue({
  el: '#app1',
  data: {
    message: 'vueのテンプレートの構文。｛｛｝｝で囲って処理がかける'
  }
})
```

htmlの方では`{{}}`の中でテンプレートの構文やif文など、またインスタンス化したdataのプロパティなども使える

```html:index.html
<div id="app1">
  {{ message }}
</div>
```

## v-bindを使った属性のバインド

- 単方向のデータバインディング
- dataの変更に応じて表示されるが、HTML側の入力でdataが変更されることはない
- `v-bind:`とすることでインスタンスで定義した`data`のプロパティが属性として出力される
- `v-bind:`は省略可能で、`:`のみで書くことができる
- class属性を渡すときは連想配列の形で渡す
- その連想配列の値が`true`か`false`かでプロパティ名が付くどうかが判別される
- この例の場合、`active`と`text-danger`が`true`なので`class="active text-danger`として入ってくる

※ `text-danger`のように`-`を使っている場合`''`または`""`で囲む必要がある


```javascript:app.js
import Vue from 'vue'

new Vue({
  el: '#app2',
  data: {
    message: 'このページをロードしたのは ' + new Date().toLocalString(),
    classObject: {
      active: true,
      'text-danger': true
    }
  }
})
```

```html:index.html
<div id="app2">
<!-- title属性は補足的な情報を与えるときに使用し、ポインタを重ねると吹き出し(ツールチップ)が表示される -->
  <span v-bind:title="message" :class="classObject">
    この文字にロードした日付が表示される
  </span>
</div>
```

## v-if, v-else-if, v-elseを使った条件分岐で表示非表示
### v-ifのみでの条件分岐

```javascript:app.js
import Vue from 'vue';

new Vue({
  el: '#app3',
  data: {
    isShow: true
  }
})
```
- `isShow`の名称は任意
- `v-if`の中で`true`か`false`かを判定し、`true`であれば、そのタグ自体を表示し、`false`であれば、そのタグ自体を非表示にする
※ 非表示の場合`DOM`自体がなくなる(`DOM`の残る`display:none`とは異なる)


```htmlindex.html
<div id="app3">
  <span v-if="isShow">v-ifを使ったDOMの表示非表示</span>
</div>
```

### v-if, v-else-if, v-elseを使った条件分岐

```javascript:app.js
new Vue({
  el: '#app3',
  data: {
    isShow: 'a'
  }
})
```


```html:index.html
<div id="app3">
  <span v-if=" isShow === 'a' ">v-ifを使った条件分岐</span>
  <span v-else-if=" isShow === 'b' ">v-ifと同階層に書く</span>
  <span v-else>v-ifと同階層に書く</span>
</div>
```

## v-showを使った要素の表示非表示

- `v-show`による要素は常に描画され、`v-if`と異なりDOMを維持する
- styleだけ非表示になっているだけ`(display: none;)`
- `v-show`が`true`なら表示、`false`なら非表示(`display:none;`)

```javascript:app.js
new Vue({
  el: '#app3',
  data: {
    isShow: true
  }
})
```

```html:index.html
<div id="app3">
  <span v-show="isShow">v-showで表示</span>
  <span v-show="!isShow">v-showで非表示に</span>
</div>
```


## v-forを使ったループ処理

`v-for`の値に変数名を割り当てる(今回は`todo`)
`todo in todos`とすることで`todo`にjsファイルで定義した`todos`の一つ一つの値のデータが入る
`data`プロパティの値を配列リテラルの形式で渡すことで、配列一つ一つを回せる


```javascript:app.js
import Vue from 'vue';

new Vue({
  el: '#app4',
  data: {
    todos: [
      {text: 'v-forで'},
      {text: 'htmlを'},
      {text: 'ループ生成'}
    ]
  }
})
```


```html:index.html
<div id="app4">
  <ol>
    <li v-for="todo in todos">
      {{ todo.text }}
    </li>
  </ol>
</div>

<!-- 出力結果 -->
<ol>
  <li>v-forで</li>
  <li>htmlを</li>
  <li>ループ生成</li>
</ol>
```

## v-onを使ったイベント発火

`v-on:`のあとにイベント名を指定し、イベント後の発火したいメソッドを指定
`v-on:`は省略可能で、`@`のみで書くことができる

- vueでは`methods`プロパティを定義でき、viewの中で使いたい関数を定義する
    - methodsは関数の置き場所として用いられる
    - methodsは関数なので、呼べばそのまま実行される
    - methodsで算出された値はキャッシュされない
- `changeMessage`という関数名は任意
- `methods`プロパティ内で`this`を使うと`data`プロパティにアクセスできる

※ `this.message`で`message`にアクセスできる


```javascript:app.js
import Vue from 'vue';

new Vue({
  el: '#app5',
  data: {
    message: 'Hello Vue.js'
  },
  methods: {
    changeMessage: function() {
      this.message = this.message + '変更しました'
    }
  }
})
```


```html:index.html
<div id="app5">
  <p>{{ message }}</p>
  <button v-on:click="changeMessage">メッセージを変える</button>
</div>

<!-- 出力結果 -->
メッセージを変える
  <!-- クリック -->
  Hello Vue.js変更しました
```

### v-onでtoggle

`v-on`では、必ずしもmethodを使わなければいけないというわけではない
以下はtoggle処理の例である

```vue:
<div id="app5">
  <button v-on:click="show = !show">
    Toggle
  </button>
</div>
```

## v-model 双方向データバインディング

- 双方向のデータバインディング
- dataの変更に応じて表示され、HTMLでフォームの入力を行った際にもdataの値の変更が可能
- `input`要素や`textarea`要素、`select`要素に双方向 (two-way) データバインディングを作成
- `v-model`で`data`のプロパティを指定すると、jsファイルのプロパティと描画が連携される
- 双方向データバインディングでは入力フォームの値が変わると描画も変わるため、`p`タグの中身もすぐに反映される
- 下の例ではinputタグの中にmessageの値がvalueの形で入る
- ユーザーの入力データが自動で更新される

```javascript:app.js
import Vue from 'vue';

new Vue({
  el: '#app6',
  data: {
    message: '双方向データバインディング'
  }
})
```

```html:index.html
<div id="app6">
  <p>{{ message }}</p>
  <input type="text" v-model="message">
</div> 
```

## computed 算出プロパティ

- `computed`は関数の処理を書く算出のプロパティである
- `methods`と似ているが、`computed`は`data`プロパティの変更に依存して描画に反映される
- dataやcomputedの値が変化することで自動的に実行
- `computed`はjsの方で算出された値が常に結果がキャッシュされており再利用できる。`data`の変更(`this`の変更)を`this`で監視しているため、`data`プロパティに変更があった場合のみ描画に反映される
    - `this`を持たない`computed`は`data`の変更を参照できず、ずっと初回のキャッシュを表示し続ける
- `methods`は再描画されるたびに処理が実行される


```javascript:app.js
import Vue from 'vue';

new Vue({
  el: '#app7',
  data: {
    isShow: true
  }
  computed: {
    showString: function() {
      return (this.isShow) ? Date.now() : 'isShowはfalse'
    },
    showString2() {
      return Date.now()
    }
  },
  methods: {
    showStringMethods() {
      return (this.isShow) ? Date.now() : 'isShowはfalse'
    },
    showStringMethods2() {
      return Date.now()
    }
  }
})
```

`computed`の場合は`()`は不要


```html:index.html
<div id="app7">
  <input type="checkbox" v-model='isShow'>
  <p>isShow: {{ isShow }}</p>
  <p>showString: {{ showString }}</p>
  <p>showString2: {{ showString2 }}</p>
  <p>showStringMethods: {{ showStringMethods() }}</p>
  <p>showStringMethods2: {{ showStringMethods2() }}</p>
</div>

<!-- 出力結果 -->
isShow: true
showString: 123456789
showString2: 123456789
showStringMethods: 123456789
showStringMethods2: 123456789
  <!-- checkbox click -->
  isShow: true
  <!-- 関数内でthisをを持つため、dataの変更を監視して反映 -->
  showString: isShowはfalse 
  <!-- dataが変更されてもthisがないので反映されず、初回のキャッシュ内容保持 -->
  showString12: 123456789
  <!-- methodsはdataに依存せず毎回再描画がされるため更新される -->
  showStringMethods: isShowはfalse 
  showStringMethods2: 234567890
```

## v-htmlでサニタイズを無効化
vue.jsでは自動でサニタイズされ、タグは文字列として読み込まれる
タグとして読み込ませたい場合はサニタイズを無効化する`v-html`を使う


```javascript:app.js
import Vue from 'vue';

new Vue({
  el: '#app10',
  data: {
    script: '<p style="color:red">タグとして表示</p>'
  }
})
```


```html:index.html
<div id="app10">
  <p>{{ script }}</p>
  <p v-html="script"></p>
</div>

<!-- 出力結果 -->
<p style="color:red">タグとして表示</p>
タグとして表示  (color:red; 適用)
```

## トランジションとアニメーション

アニメーションには`Enter`アニメーションと`Leave`アニメーションがある

- `Enter`: 非表示から表示に変わるときのアニメーション
    - 変化前の状態のclassは`v-enter`, 変化後のclassは`v-enter-to`
    - 変化過程にアニメーションを設定したいときのclassは`v-enter-active`
- `Leave`: 表示から非表示に変わるときのアニメーション
    - 変化前の状態のclassは`v-leave`, 変化後のclassは`v-leave-to`
    - 変化過程にアニメーションを設定したいときのclassは`v-leave-active`

`transition`タグで`name`属性を付けた場合は`v-`の箇所をその名前に変更できる


```javascript:app.js
import Vue from 'vue';

new Vue({
  el: '#app11',
  data: {
    show: true
  }
})
```


```html:index.html
<style>
  .fade-enter-active, .fade-leave-active {
    transition: opacity .5s;
  }
  .fade-enter, .fade-leave-to { /* .fade-leave-active below version 2.1.8 */
    opacity: 0;
  }
</style>

<div id="app11">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
```

## コンポーネントの使い方
コンポーネントとは名前付きの再利用可能な`Vue`インスタンスである(部品のようなもの)

- `Vue.component`でコンポーネントの登録
    - 第一引数にコンポーネント名を指定する、第二引数にコンポーネントに定義したい値（`data`, `template`など)を設定する
    - コンポーネントで`data`を定義するときは関数で定義する
    ※ 関数ではなくオブジェクトの形にすると複数のコンポーネントで値を共有する形になる。関数にすることで、個々のコンポーネントに値をもたせることができる
    
- コンポーネント登録後に`new Vue`でインスタンス化する


```javascript:app.js
// 登録する
Vue.component('sample-component' {
    template: '<div>this is sample</div>'
})

// インスタンス生成
new Vue({
    el: '#app'
})
```

コンポーネントを挿入したいところに呼び出すコンポーネントのタグを記述し、`app.js`を読み込む


```html:sample.html
<div id="app">
    <sample-component></sample-component>
</div>

<script src="app.js"></script>
```

HTMLが置換され、次のようになる

```html:
<div id="app">
    <div>this is sample</div>
</div>
```

### templateについて

`temaplate`はコンポーネントのHTMLを渡す
htmlファイルで`tempalte`のタグが挿入される


```javascript:app.js
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">clicked {{ count }} times</button>'
})
new Vue({el: '#app12'})
```

コンポーネント名`button-counter`にコンポーネントが割り当てられ、`template`のHTMLが挿入される


```html:index.html
<div id="app12">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>
```


#### コンポーネントの登録を省略
`slot`を使用していなければ下記のように省略できる

```vue
// 省略なし
<template>
    <div id="app">
        <app-header></app-header>
    </div>
</template>


// 省略あり
<temaplte>
    <div id="app">
        <app-header />
    </div>
</template>
```



### 単一ファイルコンポーネントを使うとき

```javascript:app.js
// 登録する
Vue.component('sample-component', require('./components/SampleComponent.vue').default);

// インスタンス生成
new Vue({
    el: '#app'
})
```


```vue:./components/SampleComponent.vue
// template作成
<template>
    //
</template>

// このtemplateを使ったときの共通の処理を追記する
<script>
export default {
    data: {
        //
    },
    mounted() {
        //
    }
}
</script>
```


### propsで親コンポーネントから受け継ぐ

- `props`を使うことでコンポーネントの親から子へ値を受け渡せる
- `props`は配列の形式で属性を定義し、親コンポーネントからその属性値を受け継いで`data`のプロパティのように振る舞う


```javascript:app.js
Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})
new Vue({el: '#app13'})
```


```html:index.html
<div id="app13">
  <!-- 親コンポーネント(コンポーネントの受け渡し先となる生成元のタグのこと) -->
  <!-- このタグを親コンポーネントといい、作成したコンポーネントを子コンポーネントという -->
  <blog-post title="title属性をpropsで渡せる"></blog-post>
  <blog-post title="title属性をpropsで渡せる"></blog-post>
  <blog-post title="title属性をpropsで渡せる"></blog-post>
</div>
```

#### オブジェクトでない配列を指定する場合

- 子が親からデータを受け取るとき、配列で指定するか、オブジェクトの配列として指定するかの２通りある
- 簡潔に列挙できるが、データの情報が記述できず、itemもvalueも同様に扱われる

```vue:parent.vue
<child
    :param0="item",
    :param1="value"
>
```

```javascript:child.vue
props: [
    'item',
    'value'
]

```

#### オブジェクトの配列として受け取る場合

- 詳細に記述できるため、違いを明記できる


```javascript:child.vue
props: {
    item: {
        type: Object
    },
    value: {
        type: Number,
        default: 0
    }
}
```




### emitで子から親へデータを伝達

- 子から親へのデータの伝搬では$emitと$onを一対として使用するカスタムイベントを活用する
- 親コンポーネントからこのイベントを監視する場合は`v-on:`, `@`でイベント名を紐付けて使用する

**子コンポーネント**

```vue:child.vue
<template>
    <div class="item">
        <span>{{ value }}</span>
        <button @click="updateEvt">追加</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            value: 0
        }
    },
    methods: {
        plus0ne() {
            this.value += 1
            // $emitの第２, 第３...引数で変数を指定するとその変数を親が受け取ることができる
            this.$emit('update', this.value)
        }
    }
}
</script>
```

**親コンポーネント**

```vue:parent.vue
<template>
    <counter @updated="updateEvt" />
</template>

<script>
import Child from './Child.vue'
export default {
    components: {
        Child
    },
    methods: {
        // 子から変数を受けとることができる
        updateEvt(value) {
            console.log('The value updated' + value)
        }
    }
}
</script>
```


### イベントとメッセージを親コンポーネントに渡す方法

- `v-on:click=$emit('カスタムイベント名')`のように`$emit`で任意のカスタムイベント名を指定することで、そのイベントをイベント名として親子コンポーネントで通知することができる
    - この例では`v-on:カスタムイベント名=func()`とすると`click`イベントが通知される

- `template`はバッククォートで囲むことで階層構造で文字列を書くことができる(ES6より)


```javascript:app.js
Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <button v-on:click="$emit('enlarge-text')">
        Enlarge text
      </button>
      <div v-thml="post.content"></div>
    </div>
  `
})
new Vue({
  el: '#app14',
  data: {
    posts: [
      {
        id: 1,
        title: 'sample post1',
        content: '<p>サンプル投稿のコンテント</p>'
      },
      {
        id: 2,
        title: 'sample post2',
        content: '<p>サンプル投稿のコンテント</p>'
      },
      {
        id: 3,
        title: 'sample post3',
        content: '<p>サンプル投稿のコンテント</p>'
      },
      
    ]
  }
})
```


```html:index.html
<div id="app14">
  <div id="blog-posts-events-demo">
    <div :style="{ fontSize: postFontSize + 'em' }">
      <blog-post
        v-for="post in posts"
        v-on:enlarge-text="fontSizeScale()"
        v-bind:key="post.id"
        v-bind:post="post"
      ></blog-post>
    </div>
  </div>
</div>
```