Vue.js to TypeScriptの書き方一覧

最近ひたすら既存のVueプロジェクトのTypeScript化をやっているのでメモとしてまとめます。

（2019/09/09 追記）
vue-class-componentはvue3のRFCから外されたようで、現在は、Composition APIがRFCとなっています。
Composition APIでのTypeScript化についてはこちらの記事をどうぞ
https://qiita.com/ryo2132/items/f055679e9974dbc3f977

# 前提
* .vueファイルで\<script lang="ts">を使う
* vue-property-decoratorを使ったデコレータ方式のTypeScript化を行う

# data
dataはそのままクラスのプロパティとして宣言します。

**JavaScript**

```vue
<script >
export default {
  name: 'Human',
  data() {
    return {
      name: '山田 太郎',
      age: 19
    };
  }
};
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  name: string = '山田 太郎';
  age: number = 19;
}
</script>
```

# components

SFCで他Vueコンポーネントを参照する際に使うComponentsは
@Component()デコレータにcomponentsをkeyに持つオブジェクトを渡し定義します.

**JavaScript**

```vue
<script>
import Header from './Header'
import Footer from './Footer'

export default {
  name: 'Page',
  components: {
    Header,
    Footer
  }
}
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Header from './Header.vue';
import Footer from './Footer.vue';

@Component({
  components: {
    Header,
    Footer
  }
})
export default class Page extends Vue {
}
</script>
```
# directive

v-hogeのような独自のdirectiveを追加する場合はそのまま@components()にオブジェクトを渡す

**JavaScript**

```vue
<script>
import HogeDirective from './HogeDirective'

export default {
  name: 'Page',
  directives: {
    hoge: HogeDirective
  }
}
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HogeDirective from './HogeDirective'

@Component({
  directives: {
    hoge: HogeDirective
  }
})
export default class Page extends Vue {
}
</script>
```
# props
親コンポーネントからデータを受け取るpropsは＠Propデコレータを使い定義します。

```
@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
```

**JavaScript**

```vue
<script>
export default {
  name: 'Post',
  props: {
    contents: { default: '', type: String, require: true },
    postNumber: { default: 0, type: [Number, String], require: true },
    publish: { default: true, type: Boolean, require: true },
    option: { type: Object, require: false } // optionには {new: boolean, important: boolean, sortNumber: number} が設定される想定
  }
}
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class Post extends Vue {
  @Prop({ default: '' })
  contents!: string;

  @Prop({ default: 0 })
  postNumber!: number | string;

  @Prop({ default: false })
  publish!: boolean;

  @Prop
  option?: {
    new: boolean,
    important: boolean,
    sortNumber: number
  };
}
</script>
```

# computed
算出プロパティはgetter付きのメソッドで定義します。

**JavaScript**

```vue
<script>
export default {
  name: 'Human',
  data () {
    return {
      firstName: '太郎',
      lastName: '山田'
    }
  },
  computed: {
    fullName () {
      return `${this.firstName} ${this.lastName}`
    }
  }
}
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  firstName: string = '太郎';
  lastName: string = '山田';

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

}
</script>
```

# emit
親コンポーネントに値を送信する$emitは@Emitデコレータを使いfunctionとして定義します。
メソッド名が、$emitに渡すイベント名がメソッド名と同様の場合は、@Emit()の引数（イベント名）は省略できます。
※ 以下のcountUpのケース。camelCaseとkebabe-caseは自動で変換されます

```vue
@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
```

**JavaScript**

```vue
<script>
export default {
  name: 'Counter',
  data () {
    return {
      count: 0
    }
  },
  methods: {
    countUp (n) {
      this.count += n
      this.$emit('count-up', n)
    },
    resetCount () {
      this.count = 0
      this.$emit('reset')
    }
  }
}
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Emit, Vue } from 'vue-property-decorator';

@Component
export default class Counter extends Vue {
  count: number = 0;

  @Emit('count-up')
  countUp(n) {
    this.count += n;
    return n;
  }

  @Emit('reset')
  resetCount() {
    this.count = 0;
  }
}
</script>
```

# model
input要素のコンポーネント化の際にvalueが競合しないように用いるmodelは
@Modelデコレータを使ってプロパティを定義します。

```
@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})
```

**JavaScript**

```vue
<script>
export default {
  name: 'MyRadio',
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: { type: Boolean }
  }
}
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Model, Vue } from 'vue-property-decorator';

@Component
export default class MyCheckBox extends Vue {
  @Model('change', { type: Boolean }) readonly checked!: boolean;
}
</script>
```


# watch
dataの変更を検知するWatchは@Watchデコレータを使ってメソッドを定義します。
optionにwatchオプションのimmediateやdeepもオブジェクトとして渡すことで指定できます。

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


**JavaScript**

```vue
<script>
export default {
  name: 'InputText',
  data () {
    return {
      text: '',
      lengthDiff: 0
    }
  },
  watch: {
    text: function (newText, oldText) {
      this.lengthDiff = newText.length - oldText.length
    }
  }
}
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';

@Component
export default class InputText extends Vue {
  text: string = '';
  lengthDiff: number = 0;

  @Watch('text')
  onTextChanged(newText: string, oldText: string) {
    this.lengthDiff = newText.length - oldText.length;
  }
}
</script>
```

#provide, inject
深い階層でも親コンポーネントと子コンポーネントでデータを共有する際に用いるprovideとinjectは@provide, @injectデコレータで定義します。

```
@Provide(key?: string | symbol) 
@Inject(options?: { from?: InjectKey, default?: any } | InjectKey)
```



**JavaScript**

Parent.vue

```vue
<script>
export default {
  name: 'Parent',
  provide: {
    commonValue: 'foo'
  }
}
</script>
```

Child.vue

```vue
<script>
export default {
  name: 'Child',
  inject: {
    commonValue: { default: 'hoge' }
  }
}
</script>
```

**TypeScript**

Parent.vue

```vue
<script lang="ts">
import { Component, Provide, Vue } from 'vue-property-decorator';

@Component()
export default class Parent extends Vue {
  @Provide() commonValue = 'foo';
}
</script>
```

Child.vue

```vue
<script lang="ts">
import { Component, Inject, Vue } from 'vue-property-decorator';

@Component
export default class Child extends Vue {
  @Inject({ default: 'hoge' })
  readonly commonValue!: string;
}
</script>
```

# mixins
コンポーネント間の共通処理を定義するミックスインはMixins()の継承を使って表現します。
ミックスイン自身もVueを継承したクラスとして定義します。
複数のミックスインを使う場合も、Mixins()の引数として与えることが可能です。
（最大5つ）
https://github.com/vuejs/vue-class-component/blob/master/src/util.ts#L35

**JavaScript**

mixinLogger.js

```js
export default {
  created () {
    const now = new Date
    console.log(`created: ${now}`)
  },
  methods: {
    log (param) {
      console.log(`log: ${param}`)
    }
  }
}
```

Human.vue

```vue
<script>
import MixinLogger from './mixinLogger'

export default {
  name: 'Human',
  mixins: [MixinLogger],
  methods: {
    greeting () {
      console.log('おはよう')
      this.log('call greeting')
    }
  }
}
</script>
```

**TypeScript**

mixin.ts

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

@Component
export default class mixinLogger extends Vue {
  created() {
    const now = new Date;
    console.log(`created: ${now}`);
  }

  log(param) {
    console.log(`log: ${param}`);
  }
}
```

Human.vue

```vue
<script lang="ts">
import MixinLogger from './mixinLogger';
import { Component, Mixins } from 'vue-property-decorator';

@Component
export default class Human extends Mixins(MixinLogger) {
  greeting() {
    console.log('おはよう');
    this.log('call greeting');
  }
}
</script>
```

# created, mounted, updated, destroyed ...etc
ライフサイクルフックはそのまま同名のメソッドとして宣言することですることで実装可能です。

**JavaScript**

```vue
<script>
export default {
  name: 'Human',
  created: function () {
    // 何か処理
  },
  mounted: function () {
    // 何か処理
  },
  updated: function () {
    // 何か処理
  },
  destroyed: function () {
    // 何か処理
  }
}
</script>
```

**TypeScript**

```vue
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  created() {
    // 何か処理
  }

  mounted() {
    // 何か処理
  }

  updated() {
    // 何か処理
  }

  destroyed() {
    // 何か処理
  }
}
</script>
```

# 参考
https://github.com/vuejs/vue-class-component
https://github.com/kaorun343/vue-property-decorator