# TypeScript のサポート

Vue CLI (opens new window) は、ビルトインの TypeScript ツールサポートを提供します。

# NPM パッケージ内の公式型宣言

静的型システムは、特にアプリケーションが成長するに伴い、多くの潜在的なランタイムエラーを防止するのに役立ち、これが Vue 3 が TypeScript で書かれている理由です。このことは、あなたが Vue 3 とともに TypeScript を使うために追加のツールを必要としないことを意味します - それは第一級市民 (first-class citizen) としてサポートを受けられます。

# 推奨される構成

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    // これは `this` のデータプロパティに対してより厳密な推論を可能にします
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node"
  }
}
1
2
3
4
5
6
7
8
9
10
11

コンポーネントメソッドにおける this の型チェックを活用するために strict: true (もしくは少なくとも strict フラグの一部である noImplicitThis: true) を含める必要があることに注意してください。そうでないと、this は常に any 型として扱われます。

より詳細を知るためには、TypeScript compiler options docs (opens new window) を参照してください。

# Webpack の設定

カスタムの Webpack の設定を使っている場合、 .vue ファイルの <script lang="ts"> ブロックをパースするように ts-loader を設定する必要があります:










 









// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
        },
        exclude: /node_modules/,
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      }
      ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 開発ツール

# プロジェクトの作成

Vue CLI (opens new window) は、TypeScript を使った新しいプロジェクトを作成できます。はじめるためには:

# 1. Vue CLI をまだインストールしていなければ、インストールします
npm install --global @vue/cli

# 2. 新しいプロジェクトを作成し、"Manually select features" を選択してください
vue create my-project-name

# もしすでに TypeScript をもたない Vue CLI で作成したプロジェクトがある場合、適切な Vue CLI プラグインを追加してください:
vue add typescript
1
2
3
4
5
6
7
8

コンポーネントの script の部分に言語として TypeScript が設定されていることを確認してください:

<script lang="ts">
  ...
</script>
1
2
3

また、TypeScript と JSX render 関数 を組み合わせたい場合:

<script lang="tsx">
  ...
</script>
1
2
3

# エディタによるサポート

TypeScript による Vue アプリケーションの開発のために、すぐに利用できる TypeScript サポートを提供している Visual Studio Code (opens new window) を強く推奨します。単一ファイルコンポーネント (SFCs) を使用している場合、SFC 内部での TypeScript の推論やその他の優れた機能を提供している、素晴らしい Volar 拡張機能 (opens new window) を入手してください。

WebStorm (opens new window) は TypeScript と Vue の両方をすぐに利用できるようサポートしています。他の JetBrains IDE も、最初から、または この無料プラグイン (opens new window) で、これらをサポートしています。

# Vue コンポーネントを定義する

TypeScript に Vue コンポーネントオブションのなかで適切に型を推論させるために、defineComponent グローバルメソッドでコンポーネントを定義する必要があります:

import { defineComponent } from 'vue'

const Component = defineComponent({
  // 型推論が有効になります
})
1
2
3
4
5

単一ファイルコンポーネント を使っている場合、これは一般的に次のように書かれます:

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

export default defineComponent({
  // type inference enabled
})
</script>
1
2
3
4
5
6
7

# Options API とともに使用する

TypeScript は明示的に型を定義することなく、ほとんどの型を推論できるようにあるべきです。例えば、数値である count プロパティを持つコンポーネントがある場合、文字列に特有のメソッドを呼び出すとエラーになります:

const Component = defineComponent({
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    const result = this.count.split('') // => 'split' プロパティは 'number' 型に存在しません
  }
})
1
2
3
4
5
6
7
8
9
10

複雑な型や推論の場合、タイプアサーション (type assertion) (opens new window) を使用してキャストすることができます:

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

const Component = defineComponent({
  data() {
    return {
      book: {
        title: 'Vue 3 Guide',
        author: 'Vue Team',
        year: 2020
      } as Book
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# globalProperties のための型の拡張

Vue 3 には globalProperties オブジェクト が用意されていて、任意のコンポーネントインスタンスからアクセス可能なグローバルプロパティを追加するために使用できます。例えば、 プラグイン では共有されたグローバルオブジェクトや関数を注入したい場合があります。

// ユーザの定義
import axios from 'axios'

const app = Vue.createApp({})
app.config.globalProperties.$http = axios

// あるデータを検証するためのプラグイン
export default {
  install(app, options) {
    app.config.globalProperties.$validate = (data: object, rule: object) => {
      // 対象のデータが特定のルールを満たしているかチェック
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

これらの新しいプロパティを TypeScript に伝えるために、Module Augmentation (opens new window) を使うことができます。

上記の例では、次のような型宣言を追加することができます:

import axios from 'axios'

declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $http: typeof axios
    $validate: (data: object, rule: object) => boolean
  }
}
1
2
3
4
5
6
7
8

この型宣言は同じファイル、またはプロジェクト全体の *.d.ts ファイル(例えば、 TypeScript で自動的に読み込まれる src/typings フォルダの中)に記述することができます。ライブラリやプラグインの作者は、このファイルを package.jsontypes プロパティで指定します。

宣言ファイルが TypeScript モジュールであることを確認

Module Augmentation を利用するためには、ファイルの中に少なくとも 1 つのトップレベルの importexport があることを確認する必要があります。それが単に export {} であってもです。

TypeScript (opens new window) では、トップレベルの importexport を含むファイルはすべて「モジュール」とみなされます。モジュールの外で型宣言が行われた場合、元の型を拡張するのではなく、上書きしてしまいます。

ComponentCustomProperties 型について詳しくは、@vue/runtime-core での定義 (opens new window) と、TypeScript ユニットテスト (opens new window) を参照してください。

# 戻り値の型にアノテーションをつける

Vue の型宣言ファイルの循環的な性質により、TypeScript は算出プロパティの型を推論することが困難な場合があります。この理由により、算出プロパティの戻り値の型にアノテーションをつける必要があります。

import { defineComponent } from 'vue'

const Component = defineComponent({
  data() {
    return {
      message: 'Hello!'
    }
  },
  computed: {
    // アノテーションが必要です
    greeting(): string {
      return this.message + '!'
    },

    // セッターを持つ算出プロパティのでは、ゲッターにアノテーションが必要です
    greetingUppercased: {
      get(): string {
        return this.greeting.toUpperCase()
      },
      set(newValue: string) {
        this.message = newValue.toUpperCase()
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Props にアノテーションをつける

Vue は type が定義されたプロパティについてランタイムバリデーションを行います。これらの型を TypeScript に提供するため、PropType を伴うコンストラクタをキャストする必要があります:

import { defineComponent, PropType } from 'vue'

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

const Component = defineComponent({
  props: {
    name: String,
    id: [Number, String],
    success: { type: String },
    callback: {
      type: Function as PropType<() => void>
    },
    book: {
      type: Object as PropType<Book>,
      required: true
    },
    metadata: {
      type: null // metadata is typed as any
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

WARNING

TypeScript には、関数式の型推論に 設計上の制限 (opens new window) があるため、 validator と、オブジェクトや配列の default 値に注意する必要があります:

import { defineComponent, PropType } from 'vue'

interface Book {
  title: string
  year?: number
}

const Component = defineComponent({
  props: {
    bookA: {
      type: Object as PropType<Book>,
      // 必ずアロー関数を使うこと
      default: () => ({
        title: 'Arrow Function Expression'
      }),
      validator: (book: Book) => !!book.title
    },
    bookB: {
      type: Object as PropType<Book>,
      // または明示的にこのパラメータを提供する
      default(this: void) {
        return {
          title: 'Function Expression'
        }
      },
      validator(this: void, book: Book) {
        return !!book.title
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# Emit にアノテーションをつける

発行されたイベントのペイロードにアノテーションをつけることができます。また、すべての宣言されていない発行されたイベントは、呼び出されたときに型エラーが発生します:

const Component = defineComponent({
  emits: {
    addBook(payload: { bookName: string }) {
      // ランタイムバリデーションの実行
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // 型エラー!
      })

      this.$emit('non-declared-event') // 型エラー!
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Composition API とともに使用する

setup() 関数においては、props 引数に型をつける必要はありません。setup() 関数は props コンポーネントオプションから型を推論するからです。

import { defineComponent } from 'vue'

const Component = defineComponent({
  props: {
    message: {
      type: String,
      required: true
    }
  },

  setup(props) {
    const result = props.message.split('') // 正しいです, 'message' は文字列 (string) として型づけされます
    const filtered = props.message.filter(p => p.value) // エラーが起こります: Property 'filter' does not exist on type 'string'
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# ref を型定義する

Ref は初期値から型を推論します:

import { defineComponent, ref } from 'vue'

const Component = defineComponent({
  setup() {
    const year = ref(2020)

    const result = year.value.split('') // => Property 'split' does not exist on type 'number'
  }
})
1
2
3
4
5
6
7
8
9

ときどき、ref の内部的な値の複雑な型を特定する必要があるかもしれません。デフォルトの推論をオーバーライドするために ref を呼び出すときに、単純にジェネリック引数を渡すことができます:

const year = ref<string | number>('2020') // year's type: Ref<string | number>
const year = ref<string | number>('2020') // year の型: Ref<string | number>

year.value = 2020 // OKです!
1
2
3
4

Note

ジェネリックの型が不明の場合、refRef<T> にキャストすることを推奨します。

# テンプレート参照を型定義する

ときどき、子コンポーネントのパブリックメソッドを呼び出すために、テンプレート参照にアノテーションをつける必要があるかもしれません。例えば、 MyModal という子コンポーネントにモーダルを開くメソッドがあるとします:

import { defineComponent, ref } from 'vue'

const MyModal = defineComponent({
  setup() {
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)

    return {
      isContentShown,
      open
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

この親コンポーネントからテンプレート参照を介して、このメソッドを呼び出したいです:

import { defineComponent, ref } from 'vue'

const MyModal = defineComponent({
  setup() {
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)

    return {
      isContentShown,
      open
    }
  }
})

const app = defineComponent({
  components: {
    MyModal
  },
  template: `
    <button @click="openModal">Open from parent</button>
    <my-modal ref="modal" />
  `,
  setup() {
    const modal = ref()
    const openModal = () => {
      modal.value.open()
    }

    return { modal, openModal }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

この方法でも動作しますが、 MyModal とその利用可能なメソッドについての型情報がありません。これを解決するためには、 ref を作成するときに InstanceType を使う必要があります:

setup() {
  const modal = ref<InstanceType<typeof MyModal>>()
  const openModal = () => {
    modal.value?.open()
  }

  return { modal, openModal }
}
1
2
3
4
5
6
7
8

オプショナルチェイニング (opens new window) や他の方法を使って、 modal.value が未定義でないことの確認が必要であることに注意してください。

# reactive を型定義する

reactive プロパティを型定義する場合、推論を使用できます:

import { defineComponent, reactive } from 'vue'

interface Book {
  title: string
  year?: number
}

export default defineComponent({
  name: 'HelloWorld',
  setup() {
    const book = reactive<Book>({ title: 'Vue 3 Guide' })
    // もしくは
    const book: Book = reactive({ title: 'Vue 3 Guide' })
    // もしくは
    const book = reactive({ title: 'Vue 3 Guide' }) as Book
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 算出プロパティを型定義する

算出プロパティの値は返り値の型から自動的に推論します:

import { defineComponent, ref, computed } from 'vue'

export default defineComponent({
  name: 'CounterButton',
  setup() {
    let count = ref(0)

    // 読み取り専用
    const doubleCount = computed(() => count.value * 2)

    const result = doubleCount.value.split('') // Property 'split' does not exist on type 'number'
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

# イベントハンドラを型定義する

ネイティブの DOM イベントを扱うとき、ハンドラに渡す引数を正しく型定義することが有効な場合があります。この例を見てみましょう:

<template>
  <input type="text" @change="handleChange" />
</template>

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

export default defineComponent({
  setup() {
    // `evt` は `any` 型になります
    const handleChange = evt => {
      console.log(evt.target.value) // TS ではここでエラーになります
    }

    return { handleChange }
  }
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

ご覧のように、evt 引数に正しくアノテーションをつけないと、その <input> 要素の値にアクセスしようとしたときに、TypeScript はエラーを出してしまいます。解決策はイベントターゲットを正しい型にキャストすることです:

const handleChange = (evt: Event) => {
  console.log((evt.target as HTMLInputElement).value)
}
1
2
3