VueにTypeScriptを導入する3つのやり方を比較してみた

最近TypeScriptが熱いっていう話をよく聞きますね！
っていうことで私もTypeScriptを導入するには何が必要なのか知りたくなりました。

でも、Vue.jsにTypeScriptを導入する方法が現状では3つほどあります。

- Class Style Component
- Vue.extend（後に知ったところObject Styleというらしい）
- Composition API(RFC)

それぞれの方法の導入記事はよくありますが、それらを比較してみたくなりました。
という内容のLTを半年ほど前にしたのですが、登壇資料はこちらです。
[VueでTypeScriptを始める 3つのStyle/3 style to start vue with typescript \- Speaker Deck](https://speakerdeck.com/fruitriin/3-style-to-start-vue-with-typescript)

## 今回の記事について
- TypeScript運用経験がとても浅いので、比較しきれてないかもしれません
  - 間違っているところがあれば教えて下さい！
- Vuexについては扱いません
- VueCLI v3で作ったプロジェクトで試しています

### 今回のコードの比較対象
[vue\.js \+ typescript = vue\.ts ことはじめ \- Qiita](https://qiita.com/nrslib/items/be90cc19fa3122266fd7)

この記事のコードを一度JavaScriptで書いて、
それからTypeScript化するという方法を試しました

コードはこちらにあります。（リポジトリ名が適当すぎた…）
[fruitriin/vue\-ts\-test - GitHub](https://github.com/fruitriin/vue-ts-test)
[vue\-ts\-test/Home\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-base-project/src/views/Home.vue)
[vue\-ts\-test/MyButton\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-base-project/src/components/MyButton.vue)
[vue\-ts\-test/ResetButton\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-base-project/src/components/ResetButton.vue)

## Class Style Component
Step by stepで書かれているし、元になった記事なので、この記事のほうが詳しいのですが…
[vue\.js \+ typescript = vue\.ts ことはじめ \- Qiita](https://qiita.com/nrslib/items/be90cc19fa3122266fd7)

既存のVue CLI v3のプロジェクトがある場合、`vue add typescript` でTypeScriptの関連モジュール一式がインストールできます。
対話形式で質問に答えてください。
Class Style Componentの場合は、「Use class-style component syntax？」 にYESです。

Class Style Componentで書き換える必要があるのは主に以下の通りです。

1.`<script>`を`<script lang=ts>`に書き換える
2.`vue-property-decorator`から型注釈をimportする

![スクリーンショット 2020-01-24 17.08.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/29da76be-47a1-bfd2-16b8-f64e5f857260.png)


3.`export default`を`export default class {Class名} extends Vue`に書き換える
4.classのすぐ上に`@Component`デコレータを付ける
5.子コンポーネントがある場合は`@Component()`デコレータの中に移動する
![スクリーンショット 2020-01-24 17.10.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/9bd77f7d-05d7-b4c4-afa2-395619a678e3.png)

5.dataはClassのプロパティとして定義する
![スクリーンショット 2020-01-24 17.12.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/f640118d-48db-10fa-2b28-ed1ba3061af8.png)

6.propsは`@Prop`デコレータ付きのプロパティとして定義する
![スクリーンショット 2020-01-24 17.13.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/4f78c6a2-fbab-1548-2793-5630e0aaf03e.png)

7.$emitは@Emit()デコレータ付きのメソッドにする
イベントの送出用に別のメソッドに分ける
![スクリーンショット 2020-01-24 18.10.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/1eae9e8a-4e1d-687d-1b83-01b5ab42e0d4.png)

8.computedはクラスのgetterメソッドにする
![スクリーンショット 2020-01-24 18.13.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/3ac7b5ea-6c64-4c74-8416-2863397deda6.png)

9.watchは@Watch()デコレータ付きのメソッドにする
![スクリーンショット 2020-01-24 18.13.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/b9ac78fd-88de-6e25-82a4-9ec536bf3111.png)

Class Style ComponentでのHome.vue, MyButton．vue, ResetButton.vueのコードはこちら
[vue\-ts\-test/Home\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-class-style-project/src/views/Home.vue)
[vue\-ts\-test/MyButton\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-class-style-project/src/components/MyButton.vue)
[vue\-ts\-test/ResetButton\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-class-style-project/src/components/ResetButton.vue)
かなり書き方が変わりますね。

### Class Style Componentポイント
- TypeScriptっぽい書き方になる
- Vueの公式サブプロジェクト
- Vue.js v0.0.1の頃からあるらしく、資料が豊富
  - Vue3以降、新しい情報が出てきにくくなる可能性はある 
- Vue3.xの本体に取り込まれることはない（RFCは提出されたがRejectされた）が、3.x以降もサポートされ続ける予定らしい（というのをどこかでみた？UIT Insideで聞いた？）
- 後述のVue.extend（Object Style）と混在させても特に問題はないはず


## Vue.extend（Object Style？）
[\[Vue\+TypeScript\] Vue\.extend で Vue らしさを保ちつつ TypeScript で書くときの型宣言についてまとめた \- Qiita](https://qiita.com/is_ryo/items/6fc799ba4214db61d8ab)
この記事が詳しいです。

`vue add typescript` して、`Use class-style component syntax?`にNOと答えます

1.`<script>`を`<script lang=ts>`に書き換える
2.`import Vue from 'vue'`する
3.`export default {}`を`export default Vue.extend({})` に書き換える
![スクリーンショット 2020-01-24 18.28.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/ae222a5b-ddad-7760-b67b-443995db102d.png)
4.componentsはそのまま
5.dataは全部を内包するオブジェクトの型を与える
![スクリーンショット 2020-01-24 18.30.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/02453338-da74-9a39-4183-11e5e06584fe.png)
6.propsはObjectがなければそのまま(type指定は必要）
Objectの場合は `type: Object as PropType<{ObjectType}>` で型を付ける
7.methods, watch, computed は適宜引数や戻り値に型を付ける
![スクリーンショット 2020-01-24 18.35.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/02b4036e-236f-3f8b-74c2-b2c736ddf4f7.png)

以上！おしまい。

Vue.extend()で書いたコードはこちら。
[vue\-ts\-test/Home\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-extends-project/src/views/Home.vue)
[vue\-ts\-test/MyButton\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-extends-project/src/components/MyButton.vue)
[vue\-ts\-test/ResetButton\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-extends-project/src/components/ResetButton.vue)

### Vue.extend() ポイント
- Vue.js v2.4から本体に追加された機能
- 従来の書き方にちょっと書き加えるだけでTypeScript化できちゃう
- props の型の付け方以外は本当に素直に書いて型がつけられるはず
  - この辺よくわかってないので教えて下さい
- 前述のClass Styleと混在させても問題ないはず
  - vue-property-decoratorがプロジェクトに入っていないなら、これをインストール必要はある
- 資料を探しにくいかもしれない

## Composition API (RFC)
Vue.js v3.xに取り込まれる予定のRFCです。
`yarn add @vue/composition-api`すると2.xのプロジェクトでも試すことができます。

下準備としてmain.tsにVue.use()で注入する必要があります。
```
import VueCompositionApi from from '@vue/composition-api';
Vue.use(VueCompositionApi);
```

1.`<script>`を`<script lang=ts>`に書き換える
2.`import {createComponent} from '@vue/composition-api'`する
3.`export default()`を`export default createComponent({})` に書き換え
4.componentsはそのまま
5.setup()を生やす

```vue
<script lang="ts">
import { createComponent} from "@vue/composition-api";

// @ is an alias to /src
import MyButton from "@/components/MyButton.vue";
import ResetButton from "@/components/ResetButton.vue";

export default createComponent({
  setup() {
    return {};
  },
  components: {
    MyButton,
    ResetButton
  }
});

```

6.data()をreactive()にする。reactive()メソッドの返り値をsetup()のreturnに含める

```javascript
data(){
  return {
    greatText: "Hello",
    count: 0
  }
}
```
↓

```javascript
// importするものにreactiveを追加する
import {createComponent, reactive} from '@vue/composition-api'

// setup()だけ抜き出し
setup(){
  const state = reactive({
    greatText: "Hello",
    count: 0    
  })
  return {
    state
  }
}
```

このとき、reactive()したものはdata配下のオブジェクトとして反映されているので、templateも修正する

```vue
<template>
  <div>
   <p>{{state.greatText}}</p>
   <p>{{state.count}}</p>
  </div>
</template>

```

8.propsはsetup関数の第一引数に渡される
9.emitはsetup関数の第二引数のcontextの中にある
10.methodsはsetup関数の中で定義してreturnするオブジェクトに含める

```javascript
  setup(props, context) {
    let count = 0;
    const onClick = () => {
      alert(props.great);
      count++;
      context.emit("click", count);
    };

    return {
      onClick
    };
  }
```

11.computedはcomputed関数でWrapします（第一引数にコールバック）
Note: computed関数はimportが必要です
12.watchはwatch関数にWrapします（第一引数に監視対象、第二引数にコールバック）
Note: watch関数はimportが必要です
Note: watch関数は何度setup関数内で呼んでもよいし、setup関数内でreturnする必要もない

```javascript
  setup() {
    const state = reactive({
      count: 0
    });
    const isRegulars = computed(() => {
      return state.count >= 3;
    });
    watch(
      () => state.count,
      () => {
        if (state.count === 3) {
          alert("常連になりました");
        }
      }
    );
    return {
      state,
      onMyButtonClicked,
      isRegulars
    };
```

Composition APIで書いたコードはこちら
- [vue\-ts\-test/Home\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-composition-api-project/src/views/Home.vue)
- [vue\-ts\-test/MyButton\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-composition-api-project/src/components/MyButton.vue)
- [vue\-ts\-test/ResetButton\.vue at master · fruitriin/vue\-ts\-test](https://github.com/fruitriin/vue-ts-test/blob/master/vue-composition-api-project/src/components/ResetButton.vue)


### Composition APIのポイント
- Vue3.xからVueコアに導入されるAPIです
  - 2.xでは別のライブラリを導入しないと使えません
- **すべてのコンポーネントをCompotision APIで書く必要はありません**
  - なんだか難しそう……と思った人は使わなくても大丈夫だし。
- コードの変更点自体はおおきいかも
- **thisを使わないので型安全に書きやすくなります**
- **コードの組織化に役立ちます**
  - 複数の機能を併せ持つコンポーネントを、機能ごとに分割して定義することができます


# まとめ
[あなた/プロジェクトチーム]にとって最適な方法を選ぶとよいですね！
その一助となれば幸いです。


比較してみたけど「特に好みとかないよ」「よくわかんない」って人は個人的にはVue.extend()がいいんじゃないかなあ。