【速習】vue-property-decorator

# これを書いた趣旨
実務でvue-property-decoratorを使ってVueをTypeScriptで記述する必要があり、ざっと理解した内容を個人用メモとしてまとめました。間違っている点があれば、ご指摘頂けますと幸いです。

# vue-property-decoratorの前に、Vue Class Componentから

Vue Property DecoratorのREADMEを読むと、最初にこう書かれています。

>This library fully depends on vue-class-component, so please read its README before using this library.(このライブラリは **vue-class-component に完全に依存しています。**)

つまり、vue-property-decoratorを使う前にvue-class-componentが使える状態にしておけよ、ということなので、公式VueからVue Class Componentを確認します。

##Vue Class Componentとは

- Vue Class Componentは、Vueのコンポーネントをクラススタイルの構文で作成できるライブラリ
- ``@Componentデコレータ``でクラスにアノテーション（``@``）を付けることで、**Vueを継承したクラスとして**コンポーネントのデータやメソッドを定義することができる
- 基本的な書き方
    - data：クラスの変数として書く
    - 算出プロパティ：getterとして書く
    - メソッド：普通にメソッドとして書く
    - ライフサイクルフック：名前を合わせてメソッドとして書く

例：シンプルなカウンタコンポーネント

```vuejs:main.vue
<template>
  <div>
    <button v-on:click="decrement">-</button>
    {{ count }}
    <button v-on:click="increment">+</button>
  </div>
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component' // import文でvue-class-componentを呼び出す

// Class定義と同じ構文でVueコンポーネントを定義できる
@Component
export default class Counter extends Vue { // Counterというクラス名でVueコンポーネントを出力する（定義する）
  // Classプロパティは、コンポーネントのデータになる
  count = 0

  // メソッドはコンポーネントメソッドになる
  // 2つのメソッドをCounter Class内で定義
  increment() {
    this.count++
  }

  decrement() {
    this.count--
  }
}
</script>
```

Vue Property Decoratorが提供するデコレータの他に、``@Prop``と``@Watch``などもあるので、簡単に紹介。

# 代表的なデコレータの紹介
##＠Prop:親コンポーネントからデータを受け取るpropsを指定

```typescript
@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
// Propの引数が、プロパティのオプションか、コンストラクター関数か、コンストラクター関数の配列の値がオブジェクトである
```

###Propsの復習
- 型: ``Array<string> | Object`` 例： ``[ "childComponent" ]``
- propsとは、**親コンポーネントからデータを受け取るためにエクスポートされた属性のリスト/ハッシュ**である

###Propsのオプション（``PropOptions``）は、以下が使える
- ``type``: ``String``, ``Number``, ``Boolean``, ``Array``, ``Object``, ``Date``, ``Function``, ``Symbol``,``カスタムコンストラクタ関数``、またはそれらの``配列``が使える
    - プロパティは、以下のように、（文字列の配列だけでなく）オブジェクトとして列挙できる

```javascript
// キーと値には、それぞれプロパティ名と型を設定します
props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}
```

- ``default``: ``any``
        - プロパティのデフォルト値を指定
        - プロパティが渡されない場合は、この値が代わりに使われる
        - オブジェクトまたは配列のデフォルト値は、ファクトリ関数で返す必要あり
- ``required``: ``Boolean``
    - プロパティが必須かどうかを指定
    - 本番環境以外では、この値が真なのにプロパティが渡されないとコンソールに警告が出される
- ``validator``: ``Function``
    - プロパティの値を唯一の引数として受け取る、カスタムのバリデーション関数
    - 本番環境以外では、この関数が偽を返す (つまり、バリデーションが失敗する) とコンソールに警告が出される

###コードの例
```typescript
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class SampleComponent extends Vue {
  @Prop({ type: String, required: true })
  userName: string;

  @Prop({ type: Boolean, defualt: false })
  isVisible: boolean;
}
</script>
```
は、以下と同義

```typescript
<script>
export default {
// プロパティは、オブジェクトとして表現できる
// ①userNameがキーで、{ type: String, required: true }が値となっているオブジェクトと、
// ②isVisibleがキーで、{ type: Boolean, default: false }が値となっているオブジェクトのプロパティを持つクラスである
  props: {
    userName: {
      type: String,
      required: true
    },
    isVisible: {
      type: Boolean,
      default: false
    }
  }
};
</script>
```

##＠Emit:子コンポーネントから親コンポーネントにデータを渡す$emitを指定
- ``$emit``によって装飾された関数が、その戻り値の後に元の引数が続く
- 戻り値がPromiseの値である場合は、emitされる前に解決される
- イベントの名前がイベント引数で与えられていない場合は、代わりに関数名が使用されるが、その場合、キャメルケース名はケバブケースに変換されるのに注意

```typescript
@Emit(event?: string) 
```
###Emitの復習
- ``$emit``:子コンポーネントから親コンポーネントにデータを渡すために利用する仕組み（**カスタムイベント**）で、親コンポーネントに対して**なんらかの変化が起こったこと（イベント）を通知（発火）する*もの。その際に関連するデータ（オブジェクト、アクション、値など)を添付可能
- 構文： ``this.$emit(event [...args])``
    - event:イベント名（String型）、[...args]：親コンポーネントに引き渡すデータ
        - 例：``this.$emit("任意のイベント名", 渡すデータ);`` で値を渡し、``this.$emit("任意のイベント名")``でイベント発火のみ
        - 例2：複数の値を渡すなら、``this.$emit( '任意のイベント名', { key1: value1, key2: value2} );``のオブジェクトで渡す

###コードの例

```typescript
import { Vue, Component, Emit } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  count = 0

  @Emit()
  addToCount(n: number) { // キャメルケースからケバブケースに変わります
    this.count += n
  }

  @Emit('reset') // イベント発火のみ
  resetCount() {
    this.count = 0
  }

  @Emit()
  returnValue() { // 値そのものを返す場合
    return 10
  }

  @Emit()
  onInputChange(e) {
    return e.target.value
  }

  @Emit()
  promise() { //Promiseオブジェクトを戻り値として返す場合、emitされる前に解決されます
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(20)
      }, 0)
    })
  }
}
```
は、以下と同義

```typescript
export default {
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    addToCount(n) {
      this.count += n
      this.$emit('add-to-count', n)
    },
    resetCount() {
      this.count = 0
      this.$emit('reset')
    },
    returnValue() {
      this.$emit('return-value', 10)
    },
    onInputChange(e) {
      this.$emit('on-input-change', e.target.value, e)
    },
    promise() {
      const promise = new Promise((resolve) => {
        setTimeout(() => {
          resolve(20)
        }, 0)
      })

      promise.then((value) => {
        this.$emit('promise', value)
      })
    },
  },
}
```

##@Watchプロパティを使う
- dataの変更を検知するためのメソッドを定義します。
- optionにwatchオプションのimmediateやdeepもオブジェクトとして渡すことで指定できます。

```typescript
@Watch(path: string, options: WatchOptions = {})
```

###watchオプションの復習
- 型: ``{ [key: string]: string | Function | Object | Array}``
- WatchOptionsは、キーが評価する式、値は、キーに対応するコールバックをもつオブジェクト
- 値はメソッド名の文字列、または追加のオプションが含まれているオブジェクトが取れる
- Vue インスタンスはインスタンス化の際にオブジェクトの各エントリに対して``$watch()``を呼ぶ


###コード例

```typescript
import { Vue, Component, Watch } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Watch('child')
  onChildChanged(val: string, oldVal: string) {}

  @Watch('person', { immediate: true, deep: true })
  onPersonChanged1(val: Person, oldVal: Person) {}

  @Watch('person')
  onPersonChanged2(val: Person, oldVal: Person) {}
}
```
は、以下と同義

```typescript
export default {
  watch: {
    child: [
      {
        handler: 'onChildChanged',
        immediate: false,
        deep: false,
      },
    ],
    person: [
      {
        handler: 'onPersonChanged1',
        immediate: true,
        deep: true,
      },
      {
        handler: 'onPersonChanged2',
        immediate: false,
        deep: false,
      },
    ],
  },
  methods: {
    onChildChanged(val, oldVal) {},
    onPersonChanged1(val, oldVal) {},
    onPersonChanged2(val, oldVal) {},
  },
}
```




# 参考
- [Vue Property Decorator 公式README](https://github.com/kaorun343/vue-property-decorator)
- [MDN:オブジェクトとプロパティ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Objects_and_Properties)
- [MDN:オブジェクトでの作業](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Working_with_Objects)
- [Vue.js公式：props](https://jp.vuejs.org/v2/api/#props)
- [Vue.js公式：プロパティの型](https://jp.vuejs.org/v2/guide/components-props.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E5%9E%8B)
- [Vue.js公式：プロパティの形式 (キャメルケース vs ケバブケース)](https://jp.vuejs.org/v2/guide/components-props.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E5%BD%A2%E5%BC%8F-%E3%82%AD%E3%83%A3%E3%83%A1%E3%83%AB%E3%82%B1%E3%83%BC%E3%82%B9-vs-%E3%82%B1%E3%83%90%E3%83%96%E3%82%B1%E3%83%BC%E3%82%B9)
- [Vue.js公式：TypeScriptのサポート](https://jp.vuejs.org/v2/guide/typescript.html#%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB-Vue-%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88)
- [[Vue] コンポーネント 子から親へ「$emit」で値を渡す](https://code-labo.com/snippet/2020/10/vue-component-emit-42/)
- [Vue.js to TypeScriptの書き方一覧](https://qiita.com/ryo2132/items/4d43209ea89ad1297426)
- [VueをTypeScriptで記述する方法について解説](https://www.e-loop.jp/knowledges/16/)
- [Vueのwatchプロパティの書き方・使い方について解説](https://www.e-loop.jp/knowledges/2/)