Spice Picks

Vue v3前夜のTypeScript事情をまとめた

May 11, 2020

業務でVue(Nuxt).jsを書きはじめて3ヶ月ほど経った。 それまではReact + TSのプロジェクトだったので、TSの良さは分かっているので、 今のプロジェクトにもTSを導入しようと思っていたが、どうやらVueのTS事情は複雑らしい。 Vueでプロダクトを作る場合、VuexやVue-Routerを使うことが多いが、その中で型の恩恵を受けにくかったり、そもそも書き方が複数あるようだ。 しかもVue3のリリースで、また事情が変わるらしい。

ちょっと時間ができたので、Vue3リリース前のVue + TS事情を調べてまとめておく。 今回はTSを使うためのコードの書き方と、型情報がどのように使用できるかに焦点を当てる。

VueとTypeScriptの今

2020年5月10日現在、Vue v3はベータ版であり、Vue v2.xが最新の公式となる。

この状態でコードの書き方は2通りある。 公式ドキュメントに全てが書いてあるが、 Vue.extend を使った方法と、 vue-class-component を使った方法だ。それぞれでかなり書き方が違う。

例えば、こんなVue.jsのcomponentがある場合

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>{{ saySomething() }}</p>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: {
      type: String
    },
  },
  methods: {
    saySomething() {
      const something = "something";
      return something;
    },
  },
};
</script>

Vue.extendを使うとこうなり

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>{{ saySomething() }}</p>
  </div>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  name: "HelloWorld",
  props: {
    msg: {
      type: String
    },
  },
  methods: {
    saySomething(): string {
      const something = "something";
      return something;
    },
  },
});
</script>

vue-class-componentを使うとこうなる

<template>
  <div class="hello">
    <h1>{{ messsag }}</h1>
    <p>{{ saySomething() }}</p>
  </div>
</template>

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

@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;

  saySomething(): string {
    const something = "something";
    return something;
  }
}
</script>

Vue.extend では、componentの宣言でVueクラスを拡張し、内部の関数などの返り値の型を記述するだけでよいので、通常の書き方に近い。

vue-class-componenはそもそも、Vueの記述をclass-style−syntaxで記述するために用意されているAPIであり、コードの記述が結構変わる。

vue-class-component はこれからも開発されていくようだが、Vueのv3にそのようなclass-style-syntaxでcomponentを定義するための提案がされていたがhttps://github.com/vuejs/rfcs/pull/17 却下されてしまった。

これを受けて、 Vue.extend で記述するケースが多くなっているようだ。

また、どちらのパターンでもpropsにTSの型推論を聞かせる

また、Vuexとかmixin使うときには型が自動で解決されないので、手動で型宣言をする必要があるようだ。これについては以下のLINE UITブログがわかりやすかった。

https://engineering.linecorp.com/ja/blog/vue-js-typescript-otoshidama/

Vue v3とTypeScript

前述したcomponentをVue3(Composition API)で書くと、以下のようになる。

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>{{ saySomething() }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  props: {
    msg: {
      type: String
    }
  },
  setup() {
    const saySomething = (): string => {
      const something = "something";
      return something;
    };

    return {
      saySomething
    };
  }
});
</script>

まあこれはv3でTSを使うための変更というより、単にcompositionAPIを使ってcomponentを定義する書き方の変更のほうが大きい。

composition APIについては公式docsを参照 https://composition-api.vuejs.org/#summary

もしこの書き方でpropsにTSによる型推論を聞かせてsetup内で使いたい場合は

type Props = {
  msg: string;
};

みたいな型をつくり

...
setup(props: Props) {
...

こんな感じで型を当てる必要がある。

vuexとかvue-routerはcomposition APIに対応中で、docsはまとまっていなかった・・・w

https://github.com/vuejs/vuex/tree/4.0 https://github.com/vuejs/vue-router-next

気になったらソースコードを見てみよう。

なんなら今のうちにソースコードを追っかけてればdocument自分でかけるかもしれない。

まとめ

以上触れたとおり、

  • Vue 2.xではTSの書き方は大きく2種類ある。
  • そして、vuexなどを用いるときは型推論が自動できかないので、自分で型を指定する必要がある。
  • Vue 3からはcompositionAPIの導入によりさらに書き方が変わる。各ライブラリは鋭意対応中。

って感じ。

あとの詳しいところは実際に使ってみながら調べるかなー。


Spice-Z

東京でフロントエンドエンジニアをやっているすぱいすのブログです。