ネット上のJavaScript関連の文書を読むとTypeScriptで記述したコードを使って説明している人が増えてきました。そのためTypeScriptの経験がない人にとってはTypeScriptで記述したコードを読むのにストレスを感じているかもしれません。Vue 3ではTypeScriptを使って記述されていることもあり特別な設定を行うことなくTypeScriptを利用することができるので、今後TypeScriptで記述された文書が増えてくると思います。本文書ではそのような事態に備えTypeScriptのVue3での設定方法について簡単な例を使って解説していいます。TypeScriptは型チェックを行うことが主な役割なので型に注目して読み進めてください。

エディターにはVisual Studio Codeを利用しています。またVue3のドキュメントを参考に動作確認を行っています。

また下記の文書でもTypeScriptに関する情報を公開しています。

環境構築

Vue3のインストール

vue-cliコマンドを利用していvue3のインストールを行います。インストール中にTypeScriptを選択することができるので選択することでTypeScriptを利用することができます。

プロジェクトの名前は任意の名前をつけてください。ここではvue_typescriptという名前をつけています。


 % vue create vue_typescript
Vue CLiがインストールされていない場合はインストールを行ってください。またバージョンが古い場合は最新バージョンにアップデートを行ってください。本文書では、Vue CLI v4.5.8を利用しています。

インストールを開始するとpresetの選択画面が表示されるので、Manually select featuresを選択してください。


Vue CLI v4.5.8
? Please pick a preset: 
  Default ([Vue 2] babel, eslint) 
  Default (Vue 3 Preview) ([Vue 3] babel, eslint) 
❯ Manually select features  

機能の選択画面が表示されるのここでTypeScriptを忘れずに選択してください。TypeScriptまで移動して、Spaceキーを押して選択してください。


? Check the features needed for your project: 
 ◉ Choose Vue version
 ◉ Babel
❯◉ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

次にVueのバージョンの選択画面が表示されるので、3.x(Preview)を選択してください。


? Choose a version of Vue.js that you want to start the project with 
  2.x 
❯ 3.x (Preview) 

以下オプション選択を行っていきますがすべてデフォルトの設定を選択していきます。Enterボタンを押すとデフォルトが選択されます。

class-style component syntaxの選択を聞かれるので”N”(No)を選択してください。YesかNoを選択することによる違いについては後ほど”class-style componentの選択”で説明を行っています。


? Use class-style component syntax? (y/N)  

Babel alongside TypeScriptを使うか聞かれるので”Y”(Yes)を選択します。Noを選択した場合はインストール後にbabel.config.jsファイルがプロジェクトディレクトリに作成されません。


? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) 

デフォルトのままEnterボタンをクリックします。


? Pick a linter / formatter config: (Use arrow keys)
❯ ESLint with error prevention only 
  ESLint + Airbnb config 
  ESLint + Standard config 
  ESLint + Prettier 
  TSLint (deprecated) 

ここでもデフォルトのままEnterボタンをクリックします。


? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save
 ◯ Lint and fix on commit

ここでもデフォルトのままEnterボタンをクリックします。

In package.jsonを選択した場合はeslintの.eslintrc.jsファイルの内容がpackage.jsonファイルの中に記述されます。

? Where do you prefer placing config for Babel, ESLint, etc.? 
❯  In dedicated config files 
   In package.json 

ここまでの設定は以下の通りです。最後もそのままEnterボタンをクリックするとインストールが開始されます。


Vue CLI v4.5.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Lin
ter
? Choose a version of Vue.js that you want to start the project with 3.x (Previe
w)
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfi
lls, transpiling JSX)? Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In package.json
? Save this as a preset for future projects? (y/N) No

class-style componentの選択による違い

class-style component syntaxをYesにした場合は、下記のようにApp.vueファイルの中でvue-class-componentsをimportして利用しており、Appコンポーネントがclassベースコンポーネントとして作成されています。


<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import HelloWorld from './components/HelloWorld.vue';

@Options({
  components: {
    HelloWorld,
  },
})
export default class App extends Vue {}
</script>

デフォルトのNoにした場合はdefineComponentをimportしています。export defaultの中身がdefineComponent関数でWrapされているこがわかります。TypeScriptを利用する場合はdefineComponent関数でのWrapが必須となります。


<script lang="ts">
import { defineComponent } from 'vue';
import HelloWorld from './components/HelloWorld.vue';

export default defineComponent({
  name: 'App',
  components: {
    HelloWorld
  }
});
</script>

さらに比較としてTypeScriptをインストールしていない場合も確認しておきましょう。App.vueファイルの中身は下記の通りです。HelloWorldのみimportを行い、scriptタグのlang=”ts”が設定されていないという違いがあります。


<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

vueの起動

インストールが完了したらインストールディレクトリに移動して下記のコマンドを実行してください。


 % cd vue_typescript 
 % npm run serve
//略
  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.2.138:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

ブラウザでhttp://localhost:8080/にアクセスするとVue.js + TypeScriptの文字が表示されていたら TypeScriptのインストールは完了しています。

vue3 + TypeScript
vue3 + TypeScript

shims-vue.d.tsファイル

インストールディレクトリを見るとshims-vue.d.tsファイルというものがあります。shims-vue.d.tsファイルはIDEやエディターに拡張子.vueファイルが何のファイルかというのを理解させるために利用するファイルです。

Visual Studio Codeを利用している場合にshims-vue.d.tsファイルが存在しな場合は下記のようにエラーメッセージが表示されます。デフォルトで作成されるので誤って削除しないように注意してください。

“Cannot find module ‘./App.vue’ or its corresponding type declarations. ts(2307)”

エラーメッセージが表示
エラーメッセージが表示

動作確認の準備コードの整理

動作確認のコードをわかりやすくするためにApp.vueファイルとHelloWorld.vueファイルのコードを更新します。


<template>
  <HelloWorld />
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import HelloWorld from './components/HelloWorld.vue';

export default defineComponent({
  name: 'App',
  components: {
    HelloWorld
  }
});
</script>

messageはpropsではなくdataプロパティとして定義しています。


<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'HelloWorld',
  data(){
    return {
      msg:'Hello TypeScript',
    }},
});
</script>

ブラウザで確認するとHello TypsSriptが表示されます。

Hello TypeScriptの表示
Hello TypeScriptの表示

TypeScriptの動作確認

TypeScriptの動作確認を行っていきますが基本機能はどれも共通で最初に型を設定(関数であれば引数に入る値への型、また関数を実行した後に戻り値の型)し、その型と異なる型を入力しようとしたり、異なる型が戻ってきたらエラーになるといったものです。つまり予期しないことがおこなることを見つけ出すことで本番環境でのエラーの発生を抑えるというものです。

computedプロパティ

TypeScriptがどのようにcomputedプロパティに影響するかを確認するためにdataプロパティに設定した文字数を表示するcomputedプロパティgetLengthを追加します。


  computed:{
    getLength() {
      return this.msg.length
    }
  }

しかし、TypeScriptを利用している場合はgetLengthの関数の戻り値の型の設定を行う必要があります。msgの文字列の長さを出す関数なので戻り値は数値となるため設定する型はnumberになります。下記のように関数の戻り値の型を設定することができます。


  computed:{
    getLength(): number {
      return this.msg.length
    }
  }

戻り値の型をnumberに設定したので、次に動作確認のためthis.msg.lengthからlengthを一時的に削除してください。削除することによりgetLengthの戻り値が数字から文字列になるためエラーが発生します。

Type “string”はtype “number”に割り当てることができないというメッセージが表示されます。

型のエラー
型のエラー

TypeScriptではファイルを保存する前にエディター上にメッセージが表示されるのでコードの間違いにすぐに気づくことができます。

TypeScriptでの型の設定と型が一致しない場合にどのようなエラーが表示されるか確認することができました。

型推論

dataプロパティでmsgの型を指定しませんでしたがエラーも出ず動作します。これはmsgに文字列を設定した時にTypeScriptが型を自動でStringと判断してくれるためです。msgのように明確に型がわかる場合は型を指定する必要がありません。これを型推論と呼びます。

msgのtypeがstringと判断しているので、methodsを利用してmsgの値を数値に変更しようとするとエラーメッセージが表示されます。


  methods:{
    changeMsg() {
      return this.msg = 3
    }
  },

Type “numberr”はtype “string”に割り当てることができないというメッセージが表示されます。

型推論の動作確認
型推論の動作確認

データオブジェクトへの型設定

オブジェクトのような複数のプロパティを持つ場合は型の宣言にInterfaceを利用することができます。Interfaceは設計図のようなもので実際に値は入っていませんが、どのようなプロパティと型でオブジェクトが構成されているかがわかります。

まずInterfaceでBookを宣言します。title, author, yearの3つのプロパティが含まれておりstringとnumberの型を持っています。


interface Book {
  title: string;
  author: string;
  year: number;
}

データプロパティbookを追加します。追加しましたが型の設定は何も行っていません。この状態では追加したInterfaceのBookとの関連は何もありません。


export default defineComponent({
  name: 'HelloWorld',
  data() {
    return {
      book: {
        title: 'Vue 3 Guide',
        author: 'Vue Team',
        year: 2020
      }
    }
  }
});

追加したbookを下記のようにtemplateタグに設定しても問題なく表示されます。


<template>
    <h1>{{ book.title }}/{{ book.author }}/{{ book.year }}</h1>
</template>
bookの中身を表示
bookの中身を表示

メソッドを利用してyearを2020の数値から文字列に変更してみましょう。型推論によりbookのyearは数値の型が設定されているので文字列に変更しようとするとエラーが発生します。

エラ〜メッセージが表示
エラ〜メッセージが表示

しかし、bookについては型設定を行っていないため初期値を文字列にすると変更は可能となります。初期値を文字列に変更すると今度は数値に変更しようとするとエラーが発生します。


export default defineComponent({
  name: 'HelloWorld',
  data() {
    return {
      book: {
        title: 'Vue 3 Guide',
        author: 'Vue Team',
        year: "2020"
      }
    }
  },
  methods:{
    changeYear(){
      this.book.year = "2021"
    }
  }
});

ここまではInterface Bookを利用していませんでしたが、Interfaceを利用していない場合のオブジェクトの動作が理解できたので今後はInterfaceを利用して設定を行います。bookのオブジェクトの最後にas Bookを追加しています。


data() {
  return {
    book: {
      title: 'Vue 3 Guide',
      author: 'Vue Team',
      year: 2020
    } as Book
  }
},

bookに対してInterface Bookの型が設定されたので、もしyearの2020を文字列に変更しようとするとエラーメッセージが表示されます。interface Bookを設定することで初期値を設定する際にエラーメッセージが表示されるため誤った初期値を設定することがなくなります。

エラーメッセージ
エラーメッセージ

初期値を設定後にメソッドを使って文字列を設定しようとしてもエラーが発生します。数値の場合は変更を行っても型が変わらないためエラーは発生しません。

props

dataプロパティのオブジェクトの設定方法が確認できたので次はpropsの確認を行います。

propsの場合はどのように型を設定していくのか確認していきましょう。propsについてはVue自身もpropsでtypeの設定を行うことができます。まずVueのpropsのtypeを利用して動作確認を行います。

HelloWorld.vueでpropsのmsgを設定します。型はStringで設定します。


<template>
    <h1>{{ msg }}</h1>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'HelloWorld',
  props:{
    msg:String
  }
});
</script>

App.vueでpropsのmsgにHello TypeScriptを設定します。


<template>
  <HelloWorld msg="Hello TypeScript" />
</template>

ブラウザで確認するとHello TypeScriptが表示されます。

propsを利用して表示
propsを利用して表示

App.vue側からmsgに数値を入れるとブラウザのコンソールにエラーメッセージが表示されます。ブラウザのデベロッパーツールを利用して確認してください。


<template>
  <HelloWorld :msg="100" />
</template>
コンソールにエラメッセージ表示
コンソールにエラメッセージ表示

Vueのtype設定ではなく、TypeScriptでpropsの型を設定するためにPropTypeをimportし下記のように設定を行います。設定を行うとVueのtypeと同様に動作します。App.vue側で数値を入力すると同じメッセージが表示されます。


<script lang="ts">
import { defineComponent, PropType } from 'vue';

export default defineComponent({
  name: 'HelloWorld',
  props:{
    msg:{
      type: String as PropType<string>,
    }
  }
});
</script>

propsで受け取る値が数値である場合は下記のように設定を行うことができます。


type: Number as PropType<number>,

オブジェクトのprops

Propsが先ほどのようにStringやNumberではなくObjectの場合はInterfaceを利用して型の設定を行うことができます。

propsを受け取る側のHelloWorld.vueで受け取るpropsデータのInterfaceを作成します。firstName, lastName, ageの3つのプロパティを持ちます。


interface User {
  firstName: string;
  lastName: string;
  age: number;
}

propsの名前はuserでPropTypeを利用して型を設定します。Vueのpropsの機能でrequireも設定しています。必須項目の場合に設定を行います。


props: {
  user: {
    type: Object as PropType,
    required: true,
  }
},

受け取ったpropsはcomputedプロパティを利用してブラウザに表示するため、fullNameという関数を追加し、戻り値の型はstringに設定します。


computed: {
  fullName(): string {
    return this.user.firstName + ' ' + this.user.lastName
  }
}

templateタグの中で設定したcomputedプロパティfullNameを表示させます。一緒にthis.user.ageも表示させます。


<template>
    <h1>{{ fullName }} {{ this.user.age }}</h1>
</template>

propsを渡す親側のコンポーネントApp.vueの設定を行います。dataプロパティでpersonを設定し、firstName, lastName, ageを設定しています。personデータをcomputedプロパティuserに設定します。


data(){
  return {
    person:{
      firstName: 'John',
      lastName: 'doe',
      age: 30,
    }
  }
},
computed:{
  user(): object {
    return this.person;
  }
}

templateタグの中でv-bindを利用してcomputedプロパティのuserオブジェクトをHelloWorldコンポーネントに渡します。


<template>
  <HelloWorld :user="user"/>
</template>

ブラウザで確認するとユーザ名とageを確認することができます。

ユーザ名とageが表示
ユーザ名とageが表示

TypeScriptで型の設定を行っているので、App.vue側から渡すデータの型と異なるデータを送信するとエラーメッセージが表示されます。下記ではageを文字列に変更しています。コンパイル時にType ‘string’ is not assignable to type ‘number’.というエラーが表示されます。


    person:{
      firstName: 'John',
      lastName: 'doe',
      age: '30',
    }
コンパイルはnpm run serveを実行している中でファイルの保存を行うと自動で行われます。

InterfaceをExportする

HelloWorldで作成したInterfaceはExportを行うことで親コンポーネントでもそのInterfaceを利用することができます。

親と子のコンポーネントでInterfaceを共有することで親コンポーネントで初期値を設定する際にエディターのエラーメッセージで型の間違いを確認することができます。

exportをinterfaceの前に追加します。


export interface User {
  firstName: string;
  lastName: string;
  age: number;
}

親コンポーネントのApp.vueでexportしたInterfaceをimportします。これでApp.vueでもInterfaceのUserを設定することができます。


import HelloWorld, { User } from './components/HelloWorld.vue';

InterfaceのUserをcomputedプロパティのuserで利用します。


computed:{
  user(): User {
    return this.person;
  }
}

この状態で初期値のageの値を数値から文字列に変更すると下記のエラーメッセージが表示されます。

エディターのエラーメッセージ
エディターのエラーメッセージ

computedプロパティの戻り値にUserを設定しましたが、今回はdataプロパティのオブジェクトも同じ型なのでdataプロパティにも型を設定することができます。


data(){
  return {
    person:{
      firstName: 'John',
      lastName: 'doe',
      age: 30,
    } as User
  }
},

firstName、lastNameを数値、ageを文字列に変更するとすぐにエラーが表示されます。

少ない例ですが、Vue3のdata, props, computedプロパティでのTypeScriptを利用方法を確認することができました。次回はVue3のCompositon APIでのTypeScriptの利用方法を確認していきます。