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

エディターにはVisual Studio Codeを利用しExtentionsにはVolarをインストールしています。

また下記の文書でもTypeScriptに関する情報を公開しています。TypeScriptの基礎を学習したい人におすすめの内容です。

以前はVueのプロジェクトの作成する際はVue CLIを利用することが推奨されていましたが2022年2月からVue3がデフォルトのバージョンとなりVue CLIはメンテナンスモードに入りました。新規にプロジェクトを作成する場合はViteを元にしたnpm install vute@latestコマンドを利用することが推奨されています。

本文書では現在推奨されているViteベースのプロジェクト作成ツールとVue CLIを利用した手順について説明を行っています。

環境構築(Viteベース)

Vue.jsプロジェクトを作成するためにnpm init vue@latestコマンドを実行します。npm init vue@latestを実行すると裏側ではcreate-vueが実行されます。


 % npm init vue@latest

create-vueではプロジェクト名とプロジェクトに追加する機能を聞かれるので必要な機能を選択してください。TypeScriptを利用する場合はTypeScriptの選択で”Yes”を選択してください。プロジェクト名は任意の名前をつけることができるので好きな名前をつけてください


 % npm init vue@latest
 Need to install the following packages:
  create-vue@latest
Ok to proceed? (y) y

Vue.js - The Progressive JavaScript Framework

√ Project name: ... vue-typescript
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add Cypress for both Unit and End-to-End testing? ... No / Yes
√ Add ESLint for code quality? ... No / Yes

プロジェクトの作成が完了したらプロジェクト名で設定した名前でフォルダが作成されるので移動してnpm installコマンドを実行してください。


 % cd vue-typescript
 % npm install

Vueの起動

npm installコマンドによりJavaScript関連のライブラリのインストールが行われるのでインストールが完了したらnpm run devコマンドを実行して開発サーバは起動します。


 % npm run dev
//略
  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 608ms

開発サーバが起動したらブラウザからhttp://localhost:3000/にアクセスすると下記の初期画面が表示されます。VueプロジェクトでTypeScriptを利用できる環境の構築は完了です。

create-vueで作成したVueの初期画面
create-vueで作成したVueの初期画面

環境構築(Vue-CLI)

Vue CLIを利用した場合の環境構築について説明を行っています。

Vue3のインストール

Vue CLIコマンド(バージョン5)を利用していVue3のインストールを行います。インストール中にTypeScriptを選択することでTypeScriptを利用することができます。

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


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

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


Vue CLI v5.0.1
? Please pick a preset: (Use arrow keys)
❯ Default ([Vue 3] babel, eslint) 
  Default ([Vue 2] babel, eslint) 
  Manually select features   

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


? Check the features needed for your project: 
 ◉ 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 (Use arrow 
keys)
❯ 3.x 
  2.x 

以下オプション選択を行っていきますがすべてデフォルトの設定を選択していきます。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 

ここでもデフォルトのまま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ファイルの中に記述されます。
fukidashi

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

presetを保存するかどうか聞かれるのでNを設定します。


? Save this as a preset for future projects? (y/N) N

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


Vue CLI v4.5.11
? 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)”

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

tsconfig.jsonファイル

TypeScriptをこれまで一度の使ったことがない人にとってはインストールフォルダにtsconfig.jsonと名前の見慣れないファイルも作成されています。このファイルでTypeScriptに関する設定を行うことができます。tsconfig.jsonファイルもTypeScriptを利用する上で必須なファイルです。

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

動作確認のコードをわかりやすくするためにApp.vueファイルとHelloWorld.vueファイルのコードを更新します。HelloWorldのコンポーネントのprops msg, imgタグ, stleyタグを削除しています。


<template>
  <HelloWorld />
</template>

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

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

HelloWorldコンポーネントではmsgはpropsではなくdataプロパティとして定義します。

どちらのファイルにもscriptタグのlang属性とdefineComponent関数でコンポーネントを囲んでいること以外に通常のJavaScriptとのコードとの違いはなく型に関する設定は見当たりません。lang属性は必須で設定値のtsはtypescriptを表しています。

dataプロパティのmsgは文字列なので型はstringですが、設定を行わなくても型推論により自動に型がstringに設定されています。型推論はTypeScriptが自動で行います。


<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>

型推論による型を確認したい場合はmsgプロパティにカーソルを合わせるとエディターの機能によりmsgの型がstringになっていることが確認できます。

型推論の確認
型推論の確認

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

Hello TypeScriptの表示
Hello TypeScriptの表示

TypeScriptの動作確認

TypeScriptを設定しながら動作確認を行っていきますが基本的に行うことはいずれも共通で最初に型を定義して設定(関数であれば引数に入る値への型、また関数を実行した後に戻り値の型)し、その型と異なる型を入力しようとしたり異なる型が戻るようなコードを記述するとエラーメッセージで教えてくれるといったものです。つまり実行時にエラーを見つけるのではなくコードを記述する際にエラーを見つけることで本番環境でのエラーの発生を抑えることができます。

変数の型の設定方法

TypeScriptでは変数を定義する際にその変数にどの型(タイプ)を持った値が入るのか設定することができます。頻繁に利用する型としてString, Number, Array, Boolean, Function, Objectなどがあります。そのほかにもany型やvoid型、リテラル型などがありますが。まずは先に出てきた6つの型(タイプ)を利用するためにTypeScriptを使ってどのように型の指定を行うのか確認していきます。

JavaScriptの場合は変数を定義して各変数に値を設定するだけです。特に型に関して明示的に設定を行うことはありません。そのため最初に文字列を設定し後ほど数値を設定するといったことが可能です。


let city = "Tokyo"
let temp = 20;
let loading = true;
//文字列、数値など型を気にしない
city = 40;

TypeScriptで型の指定を行う場合は変数の名前の後ろに:(コロン)をつけて型を指定します。変数cityは文字列が入るのでstring型, tempは数字が入るのでnumber型, loadingはtrueかfalseの値のいずれの値しか入らないのでboolean型を設定します。型を定義した後に異なる型を設定しようとするとエラーメッセージが表示されます。


let city: string = "Tokyo"
let temp: number = 20;
let loading: boolean = true;
// 下記はエラー
city = 20

次に配列を確認します。これまでの流れからすると配列は下記のように記述できそうな感じがします。


let cities: array = ['Tokyo','Paris','London'];

配列の場合は要素の中に入る値の型の指定を行います配列の要素の型がすべてstringなどで下記のように記述します。配列の記述方法には2つあり、2つ目はジェネリクスを利用しています。


let cities: string[] = ['Tokyo','Paris','London'];
or 
let cities; Array = ['Tokyo','Paris','London'];

stringではなくbooleanの配列の場合は下記のように記述します。


let a: boolean[] = [true,false,false];
or 
let a: Array = [true,false,false];

関数の場合はどのように型を設定するか確認するために引数に入れた2つの値を足してその結果を戻す関数addを使います。関数を見ると型を指定できそうな箇所が2つあることがわかります。1つは引数、2つ目は関数から返される値です。


function add(a,b){
  return a + b;
}

TypeScriptで型を指定すると下記のように記述することができます。add関数はシンプルなのであまり有益性は感じないかもしれませんが内部の処理が複雑な場合はadd関数はa, bの数値を入れると数値が戻ってくると型の指定を見ればわかるのでadd関数を再利用する際に役立てることができます。TypeScriptを利用するとコードの可読性がよくなるといわれているのはこのことからもわかります。その反面TypeScriptを理解せずコードを見ると型がただの記号にしか見えず理解するのが困難になります。


function add(a: number,b: number): number{
  return a + b;
}
//アロー関数
const add = (a:number, b:number):number{
  return a + b;
}

引数と関数の戻り値に型を設定することがわかりましたが関数自体にも型を設定することができます。関数名の右側に:で関数の型を設定しています。(a: number, b: number) => numberの部分が関数に設定した型です。


const add: (a: number, b: number) => number = (
  a: number,
  b: number
): number => {
  return a + b;
};

オブジェクトを持つ変数userに型を設定することでオブジェクトへの型の設定を確認します。


const user = {
  id: 100,
  name: 'John Doe',
};

オブジェクトを持つuserにはこれまで出てきたstring型, number型を直接設定することはできません。その代わりにオブジェクトが持つプロパティに対して型を設定します。オブジェクトとは異なりプロパティと値毎に”,”(カンマ)を設定するのではなく”;”セミコロンを利用します。セミコロンはプロパティと型のペア毎に改行する場合は必須ではありませんが1行に型の設定を記述したい場合は必須となります。


const user: {
  id: number;
  name: string;
} = {
  id: 100,
  name: 'John Doe',
};

1行に並べるのは以下のような場合です。


const user: { id: number; name: string } = {
  id: 100,
  name: 'John Doe',
};

computedプロパティ

TypeScriptで使われる基本な型の説明が終わったのでVue特有の機能であるcomputedプロパティでの型の設定について確認していきます。

TypeScriptがどのようにcomputedプロパティに影響するかを確認するためにdataプロパティに設定した文字数を表示するためHelloWorld.vueファイルに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ではエディターの機能によりファイルを保存する前にメッセージが表示されるのでコードの間違いにすぐに気づくことができます。

computedプロパティへの型の設定方法とTypeScriptで設定した型とgetLengthで戻される値の型が一致しない場合にどのようなエラーが表示されるか確認することができました。

型推論

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

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


methods: {
  changeMsg() {
    this.meg = 3;
  },
},

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

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

changeMsgメソッドに引数がある場合を確認します。引数のmsgがstringの場合はdataプロパティの型もstringなので問題もなく処理ができます。


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

引数のmsgにstringとは異なる型であるnumberを設定した場合には、Type ‘number’ is not assignable to type ‘string’のメッセージが表示されます。


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

dataプロパティへの明示的な型設定

dataプロパティにmsgを追加し文字列を設定すると型推論により自動に型が設定されました。

明示的に型を設定したい場合は下記のように設定することができます。


data(): {
  msg: string;
} {
  return {
    msg: 'Hello TypeScript',
  };
},

asを利用して下記のように設定することもできます。asを利用して型を設定する方法は型アサーションと呼ばれます。


data() {
  return {
    msg: 'Hello TypeScript' as string,
  };
},

設定した値(下記では文字列)とは異なる型を設定した場合はエラーメッセージが表示されます。


data() {
  return {
    msg: 'Hello TypeScript' as number,
  };
},
異なる型を設定した場合
異なる型を設定した場合

msgに文字列だけではなく数値も入れたいという場合はunion型で記述することができます。この場合はmsgの値が文字列でも数値でもエラーメッセージは表示されません。


data() {
  return {
    msg: 'Hello TypeScript' as number | string,
  };
},

changeMsgメソッドの引数にもunion型を設定することができます。dataプロパティにnumberとstringを設定した後にchangeMsgの引数に必ずnumberとstringを設定する必要があるのではなくstringを持つ値でもnumberを持つ値でも設定することができます。


  methods:{
    changeMsg(msg: number | string) {
      this.msg = msg
    }
  },

メソッドの型

computedプロパティと同様にメソッドの戻り値に明示的に型を設定することができます。メソッドに引数がある場合は引数にも型を設定することができます。

型推論により型が自動で設定されるため型の設定を行わなくてもエラーメッセージは表示されていません。メソッドの名前にカーソルを合わせると戻り値に何もない時に設定することができるvoid型が設定されています。


methods: {
  changeMsg() {
    this.msg = 'Hello World';
  },
},
型の確認
型の確認

メソッドの場合は引数を設定することができます。引数に型を設定していない場合はanyが設定されます。

引数に型の指定がない場合
引数に型の指定がない場合

anyはどのような値でも設定できることから型の間違いを判定することができなくなりバグに繋がる可能性があるので引数には適切な型を設定します。引数のmsgに適切な型であるstring型を設定します。

メソッドの型の確認
メソッドの型の確認

changeMsgでreturnでmsgを戻すように変更を行います。


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

再度メソッドにカーソルを合わせるとメソッドの戻り値がvoidからstringに変わっていることがわかります。

メソッドの戻り値の型の確認
メソッドの戻り値の型の確認

メソッドの戻り値に明示的に型を設定することもできます。


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

メソッドによって戻される型と戻り値の型を異なる型のnumber型に変更するとメッセージが表示されます。戻り値に設定したnumberではなくreturn msgにメッセージ表示されます。コードの中身からnumber型が戻り値にならないことがわかるためエラーとなります。


methods: {
  changeMsg(msg: string): number {
    this.msg = msg;
    return msg;
  },
},
メソッドの戻り値のエラー
メソッドの戻り値のエラー

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

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

interfaceを利用して型の設定方法を確認します。まずinterfaceでBook型を宣言します。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 を利用し、interface を設定しない場合との違いを理解していきましょう。データプロパティに型を明示的に設定したいのでデータプロパティに型を設定します。


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

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

asを利用してBookの型を設定することもできます。


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

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

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

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

型エイリアス

interface以外にもオブジェクトに型を設定したい場合に利用できるtypeがあります。typeは型エイリアスと呼ばれエイリアスという名前がついているように型に別名をつける時に利用することができます。typeを利用することでnumberの型にYearという別名をつけています。typeで作成した別名のYearを使って型を設定することができます。下記ではYearという名前のnumber型の別名をつけることでbirthday変数の型にYear型を設定できるようになります。Yearを設定することでnumber型を設定することになります。


type Year = number;
const birthday: Year = 2000;

typeはinterfaceと同様にオブジェクトに対して型を設定することができます。interfaceとは定義の記述方法が似ていますがtypeでは=(イコール)が使われたり、最後の閉じるカーリーブレースの後ろに;(コロン)がつきます。


type Book =  {
  title: string;
  author: string;
  year: number;
};

typeは下記のように決められた複数の値を持つ変数のunion型設定に利用することができます。injectにはpfizer, moderna, astrazeneca以外を入れようとするとエラーになります。


Type VACCINE = 'pfizer' | 'moderna' | 'astrazeneca';
const inject: VACCINE = 'pfizer'

interfaceやtypeの違いなどは下記の文書に記述しているのでぜひ参考にしてみくてだい。

propsの場合

dataプロパティのオブジェクトの設定方法が確認できたので次はpropsの場合はどのように型を設定していくのか確認していきましょう。

propsについてはVue自身にもpropsで型の設定を行う機能を持っています。まずVueが持つpropsでの型の設定方法を利用して動作確認を行います。

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>
コンソールにエラメッセージ表示
コンソールにエラメッセージ表示

実行した時に型に問題があることがわかります。

数値でpropsを渡す場合は:(コロン)を忘れずに付与してください。付与しない場合は文字列として渡されます。
fukidashi

Vue.jsが持つ型設定ではなくTypeScriptでpropsの型を設定する場合はPropTypeをimportしPropTypeのジャネリクスを使って型を設定します。


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

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

App.vueファイルから文字列ではなく数値を渡そうとするとpropsのPropTypeで設定した型と一致しないためエラーメッセージが表示されます。先ほどのようにVueの機能のみで型を設定した時のように動作確認をして初めてエラーが表示されるということはありません。

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.jsが持つpropsの機能でrequireも設定しています。requiredは親から渡されるpropsが必須の場合に設定を行います。


props: {
  user: {
    type: Object as PropType<User>,
    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を実行している中でファイルの保存を行うと自動で行われます。
fukidashi

InterfaceをExportする

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

別のコンポーネントファイルで記述したinterfaceだから利用できるわけではなく別ファイルでinterfaceを定義してexportを行えば他のコンポーネントでimportすることができます。
fukidashi

親と子のコンポーネントで同じ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を文字列に変更するとすぐにエラーが表示されます。

VueのOptions APIを利用した場合のVue3環境におけるTypeScriptの設定例でしたがVueのアプリケーションを構築する上で必須の要素であるdata, props, computed, methodsでのTypeScriptを利用方法を確認することができました。

Composition APIとTypeScript

これまではOptions APIを利用してTypeScriptの設定方法を確認してきました。Vue3からComposition APIを利用してコードを記述することができます。ここからはComposition APIを利用した場合のTypeScriptの設定方法を説明していきます。まだComposition APIに慣れていない人もいると思うのでComposition APIの説明も各所で入れています。

Reactiveでの設定


<template>
  <div class="app">
    {{ state.msg }}
  </div>
</template>

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

export default defineComponent({
  name: 'App',
  components: {},
  setup() {
    const state = reactive({
      msg: 'Hello TypeScript',
    });
    return {
      state,
    };
  },
});
</script>

setup関数を追加してその中でreactive関数を利用してリアクティブな変数を定義します。

ブラウザ上にはHello TypeScriptが表示されます。リアクティブな変数msgを定義した時に型を設定していますせんが型推論によってstringが設定されていることが確認できます。

reactiveでdataプロパティを設定
reactiveでdataプロパティを設定

型を明示的に指定したい場合はasを利用して行います。


const state = reactive({
  msg: 'Hello TypeScript' as string,
});

stringからnumberに変更するとエラーメッセージが表示されます。

reactive関数を利用している場合はtoRefsによってstateをrefに変換することができます。refに変換することでstate.msgをmsgに変更することができます。型への影響はありません。


<template>
  <div class="app">
    {{ msg }}
  </div>
</template>

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

export default defineComponent({
  name: 'App',
  components: {},
  setup() {
    const state = reactive({
      msg: 'Hello TypeScript' as string,
    });
    return {
      ...toRefs(state),
    };
  },
});
</script>

stateにはsetup関数の中でアクセスすることができます。msgの型はstringなのでstate.msgに数値を設定するとエラーメッセージが表示されます。


setup() {
  const state = reactive({
    msg: 'Hello TypeScript' as string,
  });

  state.msg = 25;
state.msgに数値を設定
state.msgに数値を設定

reactive関数はジェネリックを使って型を設定することができます。


  const state = reactive<{ msg: string }>({
    msg: 'Hello TypeScript',
  });

さらにinterfaceを利用して型を別に定義してその型をreactive関数の型に利用することができます。


interface State {
  msg: string;
}
//略
    const state = reactive<State>({
      msg: 'Hello TypeScript',
    });

refでの設定

reactive関数からref関数に変更を行いリアクティブな変数msgの設定を行います。


<template>
  <div class="app">
    {{ msg }}
  </div>
</template>

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

export default defineComponent({
  name: 'App',
  components: {},
  setup() {
    const msg = ref('Hello TypeScript');
    return {
      msg,
    };
  },
});
</script>

上記のように設定を行うと型推論によりstringが設定されます。

型推論によりstringに設定

refの場合に型を設定する場合、ジェネリックで型を設定することができます。


const msg = ref<string>('Hello TypeScript');

refで定義したデータにアクセスする場合は.valueをつける必要があります。


msg.value="Hello Vue 3";

refでオブジェクトの設定

オブジェクトの型を設定する場合はinterfaceを利用します。interfaceの設定についてはOptions APIの時と同じです。


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

refで型のBookで作成する場合はジェネリックで設定します。interfaceで設定した型で初期値を設定することができます。


const book = ref<Book>({
  title: 'Vue 3 Guide',
  author: 'Vue Team',
  year: 2020,
});

異なる型を設定した場合にはエラーメッセージが表示されます。

型とは異なる値を設定した場合
型とは異なる値を設定した場合

外部リソースからbook情報を取得する場合には配列で保存します。配列の場合は以下のように型を設定することができます。


const books = ref<Book[]>([
  {
    title: 'Vue 3 Guide',
    author: 'Vue Team',
    year: 2020,
  },
  {
    title: 'Vite Guide',
    author: 'Vue Team',
    year: 2021,
  },
]);

propsの場合

propはComposition APIでもOptions APIの場合でも利用方法が同じなので型の設定も同じです。HelloWorldコンポーネントを使って確認します。

HelloWorld.vueファイルに以下を記述します。propsに型を設定する場合はPropTypeをimportしてます。


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

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

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

refで定義したmsgプロパティをpropsとしてHelloWorldコンポーネントに渡しています。


<template>
  <div class="app">
    <hello-world :msg="msg" />
  </div>
</template>

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

export default defineComponent({
  name: 'App',
  components: {
    HelloWorld,
  },
  setup() {
    const msg = ref('Hello TypeScript');

    return {
      msg,
    };
  },
});
</script>

メソッド(関数)の場合

Options APIではmethodsの中に関数を記述していましたがComposition APIではsetup関数の中に関数を作成することができます。msgの値を変更するとchangeMsg関数を追加する。msg.valueで値を変更することが可能です。追加した関数をtemplateタグの中で利用するためにはreturnのオブジェクトの中にchangeMsgを含める必要があります。


setup() {
  const msg = ref('Hello TypeScript');

  const changeMsg = () => {
    msg.value = 'Hello Vue';
  };

  return {
    msg,
    changeMsg,
  };
},

型推論によってmsgの型がstringに設定されているので上記のコードでは問題はありませんがmsg.valueの値を数値に変更するとエラーメッセージが表示されます。

数値を変更するとエラー
数値を変更するとエラーC

Composition APIの場合もreactive, ref, props, 関数でのTypeScriptの設定方法の基礎を確認することができました。