TypeScript + Vue.js の始め方

f:id:youtatan:20190426181226p:plain

こんにちは。エンジニアの小椋です。
最近になって初めてTypeScriptを使う機会があり、TypeScriptもっと使おう!😠😠😠と思ったので、今回はVue.jsでTypeScriptを使う方法を紹介したいと思います!

TypeScriptを使ったVue.jsプロジェクトの始め方

vue-cli 3.0からはvue createの時に対話形式でTypeScriptを使うかどうかを選択することができるようになりました。
今回はそれを使って初めからTypeScript標準搭載のプロジェクトを作ります。

vue create vue-ts

すると

? Please pick a preset

と聞かれるので、Manually select featuresを選びます。
ラジオボタンが出てくるので、スペースキーで◉TypeScriptにチェックを入れ、エンターキーで確定します。
今回は◉Routerもプリセットで追加しておきました。

ここで◉TypeScriptにチェックが入っていると、

? Use class-style component syntax? (Y/n)

と聞かれます。
後で解説するクラス記法を使用するかどうかの質問なのですが、とりあえずYにします。
(class-style component syntaxを使用する場合、vue-class-componentモジュールが追加で必要になります。Yを選択すると、プリセットの生成時に自動でインストールされます。)

残りの質問も全部Yにしておきましょう。
これでTypeScriptでVueを書く準備は完了です!👏👏👏

.
├── README.md
├── babel.config.js
├── node_modules
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── src
├── tsconfig.json
└── tslint.json

ルートディレクトリにtsconfig.jsontslint.jsonなどTypeScript関連のファイルが生成されているのが確認できます。

開発サーバーを起動しておきましょう。TypeScriptでも、当然ホットリロード 機能が利用できます!

npm run serve

f:id:youtatan:20190426181508p:plain

TypeScriptじゃないHelloWorldとちょっと中身が違いますね。

TypeScriptを使った.vueファイルの書き方

Vue.jsを使ったことがある人は、src/components/HelloWorld.vueなどを見ると、よく知っている.vueファイルと若干様子が違うことが分かると思います。

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;
}
</script>
  • scriptタグ部分にlang="ts"と指定がある

lang="ts"を書き忘れると、そのvueファイルでTypeScriptの記法は使えません。コンパイルエラーになります。

  • Vueオブジェクトをextendsしたclassベースの記法になっている

こちらが今回の本題です。
Vue.jsでは今までオブジェクト形式でdatamethodsなどを全て定義して、Vueインスタンスをnewする特有な書き方に馴染みがあると思います。
TypeScriptではそれとは違い、よりTypeScript FriendlyなClass Style Vue Component記法を利用することができます。

Class Style Vue Component

ここからは、Class Style Vue Component(以下クラス記法)の書き方を紹介します。
vue-routerをプリセットで選択した時に入っているsrc/views/About.vueファイルを編集していきます。

コンポーネントの作成

通常vueファイルで、Vueコンポーネントを作成する場合、

<script>
export default {
  name: 'About',
}
</script>

このように書きますが、クラス記法の場合は以下のように書きます。

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class About extends Vue {}
</script>

@Componentは、vue-property-decoratorという公式サポートのライブラリを利用して、直下で宣言されているクラスがVueコンポーネントであることを示すアノテーションです。

dataの定義

ここからはコンポーネントのプロパティの書き方です。

早速ですが、Vue.jsのdataプロパティの定義、まわりくどいですよね?
初めて触った時dataプロパティはfunctionにしろって言われて???ってなりましたよね?

<script>
export default {
  name: 'About',
  data() {
    return {
      id: 0,
    }
  }
}
</script>

でもクラス記法なら単なるクラスのメンバ変数として書けるので、スッキリです!👏👏👏

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class About extends Vue {
  id: number = 0
}
</script>

methodsの定義

いつものmethods

<script>
export default {
  methods: {
    myMethod(id) {
      // 処理
    }
  }
}
</script>

クラス記法ではメンバ関数として書けます。

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class About extends Vue {
  myMethod(id: number): void {
    // 処理
  }
}
</script>

computedの定義

いつものcomputed

<script>
export default {
  computed: {
    myCalc() {
      // 処理
    }
  }
}
</script>

クラス記法ではgettersetterとして書くことでcomputedプロパティを表すことができます。

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class About extends Vue {
  get myCalc(): number {
    // 処理
  }
  set myCalc(): void {
    // 処理
  }
}
</script>

ライフサイクルフックの定義

JavaScriptの場合

<script>
export default {
  created() {
    // 処理
  },
  mouted() {
    // 処理
  },
  destroyed() {
    // 処理
  },
  // ...
}
</script>

クラス記法では特定の名前のクラスメンバメソッドとして書くことでライフサイクルフックを定義できます。

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class About extends Vue {
  created(): void {
    // 処理
  }
  mounted(): void {
    // 処理
  }
  destroyed(): void {
    // 処理
  }
  // ...
}
</script>

props、emit、watchなどの定義

クラス記法では、これらはvue-property-decoratorでアノテーションとしてサポートされています。

<script lang="ts">
import { Component, Prop, Emit, Watch,  Vue } from 'vue-property-decorator';
@Component
export default class About extends Vue {
  @Prop() myProp!: string  // !は必須です
  @Emit('my-event') // 発火させるイベント名を文字列で指定
  myEvent(v: string): void {}
  emitMyEvent() {
    this.myEvent('hello')
  }
  @Watch('myProp') // 監視するメンバを文字列で指定
  onChangeMyProp(val: string, old: string): string {
    // 処理
  }
}
</script>

なんか@Emitはちょっとくどい感じしますね。ちなみにクラス記法でもthis.$emit('my-event', 'hello')は使えます。

@Componentについて

@Componentに引数を渡すと、new Vue()するときと同じような挙動になります。
つまり、

@Component({
  props: ['one', 'two'],
  data() { return { hoge: 'hoge' } },
  methods: {
    piyo: function (v) {
      this.$emit('piyo-event', 'piyopiyo')
    }
  }
})
export default class HelloWorld extends Vue {}

みたいなことができます。

何に使うんだ、、、って感じですが、filtersとかアノテーションが用意されていないプロパティは@Componentに渡すことで実装できます。

(クラスと離れたところに書かなきゃいけないので多少可読性が落ちますね・・・)

まとめ

Vue.jsでTypeScriptを使うメリット

これは一般的にTypeScriptを使うメリットとほとんど同じです。
あとメリットとしては、個人的にはクラス記法は可読性が高いと思うので好みです。TypeScript慣れしている人やオブジェクト指向言語の開発に馴染みのある人ならなおさらそうだと思います。

でも既存のVueプロジェクトに追加でTypeScriptを導入する場合なんかは、通常の記法で静的型付けを行うのがいいのではないでしょうか。

// これも書ける(lang="ts"必須)
export default {
  methods: {
    foo(v: string): void {
      console.log(v) 
    } 
  }
}

書き方をほぼ変えずにTypeScriptのメリットも享受できるので、Vue.jsの記法に慣れている人にはとても分かりやすいですね!👍
まだVSCodeの拡張機能(Vetur)でのコードスニペットがうまくいかなかったりする部分はありますが、行く行くはVue.js + TypeScriptでの開発環境がもっと整って一般的になることを個人的には期待しています!👏👏👏👏👏