TypeScriptはJavaScriptにデータの型付けを追加した言語で、Microsoftによって開発されました。Vue.jsプロジェクトでTypeScriptを導入することで、コードの品質向上、バグの早期発見、IDEのコード補完機能強化など多くのメリットが得られます。Vue 3はTypeScriptでゼロから書き直されており、型のサポートが大幅に改善されています。この章では、Vue.jsプロジェクトにTypeScriptを導入する方法と基本的な使い方について解説します。
TypeScriptはJavaScriptの上位集合(スーパーセット)で、静的型付けや型推論などの機能を追加しています。これにより、開発時にエラーを早期に発見でき、コードの可読性や保守性が向上します。すべてのJavaScriptコードは有効なTypeScriptコードですが、TypeScriptはさらに型定義や型注釈などの機能を提供します。
既存のVue.jsプロジェクトにTypeScriptを導入する場合と、最初からTypeScriptを使ってプロジェクトを作成する場合の2つのパターンがあります。
npm create vue@latest my-vue-ts-app
# プロジェクト作成時の質問で、TypeScriptを選択:
# ✓ Add TypeScript? Yes
# TypeScriptと関連パッケージをインストール
npm install -D typescript @vue/tsconfig @types/node
次に、プロジェクトルートにtsconfig.json
ファイルを作成します:
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"strict": true
}
}
Vue 3では、Composition APIとTypeScriptの相性が非常に良いです。以下の例で基本的な使い方を見てみましょう。
<script setup lang="ts">
import { ref, computed } from 'vue'
// 型注釈を使用した変数定義
const message = ref<string>('Hello TypeScript')
const count = ref<number>(0)
// 型推論も可能(明示的な型注釈なし)
const doubleCount = computed(() => count.value * 2)
// 関数に型を定義
function increment(): void {
count.value++
}
// インターフェースの定義
interface User {
id: number
name: string
email?: string // オプショナルプロパティ
}
// インターフェースを使用
const user = ref<User>({
id: 1,
name: '山田太郎'
})
</script>
<template>
<div>
<h1>{{ message }}</h1>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
<div>
<h2>User Info</h2>
<p>ID: {{ user.id }}</p>
<p>Name: {{ user.name }}</p>
</div>
</div>
</template>
コンポーネント間の通信では、PropsとEmitsに型を定義することで安全なデータのやり取りが可能になります。
<script setup lang="ts">
// definePropsに型を定義する方法
const props = defineProps<{
title: string
likes?: number
isPublished: boolean
commentIds: number[]
author: {
name: string
email: string
}
}>()
// またはインターフェースを使用
interface Author {
name: string
email: string
}
interface PostProps {
title: string
likes?: number
isPublished: boolean
commentIds: number[]
author: Author
}
// 上記のインターフェースを使用
const propsWithInterface = defineProps<PostProps>()
</script>
<script setup lang="ts">
// defineEmitsに型を定義
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 使用例
function handleChange(id: number) {
emit('change', id)
}
function handleUpdate(value: string) {
emit('update', value)
}
</script>
TypeScriptではリアクティブなデータの型を明示的に定義できます。
<script setup lang="ts">
import { reactive, ref, computed } from 'vue'
// refを使う場合
const count = ref<number>(0)
const message = ref<string>('Hello')
const items = ref<string[]>([])
// reactiveを使う場合
interface User {
id: number
name: string
email: string
isActive: boolean
}
const user = reactive<User>({
id: 1,
name: '山田太郎',
email: 'yamada@example.com',
isActive: true
})
// computedプロパティの型推論
const userName = computed(() => user.name.toUpperCase())
// 戻り値の型は自動的にstring型として推論される
</script>
Vue Routerを使用する際も型の恩恵を受けることができます。
// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'
// ルート定義に型を追加
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: AboutView
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router
コンポーネント内でのルーターの使用:
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'
// 型付きルーターとルートパラメータ
const router = useRouter()
const route = useRoute()
// IDパラメータを取得(型安全)
const id = route.params.id as string
// ルーターのメソッドを使用
function navigateToHome() {
router.push({ name: 'home' })
}
function navigateToUser(userId: number) {
router.push({ name: 'user', params: { id: userId.toString() } })
}
</script>
Piniaは最初からTypeScriptをサポートしており、型安全な状態管理を実現できます。
// stores/counter.ts
import { defineStore } from 'pinia'
// インターフェースでストアの状態の型を定義
interface CounterState {
count: number
name: string
}
export const useCounterStore = defineStore('counter', {
state: (): CounterState => ({
count: 0,
name: 'Counter'
}),
getters: {
doubleCount(): number {
return this.count * 2
}
},
actions: {
increment() {
this.count++
},
updateName(name: string) {
this.name = name
}
}
})
コンポーネント内でのPiniaストアの使用:
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
// ストアを使用
const counterStore = useCounterStore()
// storeToRefsで状態をリアクティブに分解
const { count, name } = storeToRefs(counterStore)
// アクションを使用
function handleIncrement() {
counterStore.increment()
}
function updateStoreName(newName: string) {
counterStore.updateName(newName)
}
</script>
<template>
<div>
<h2>{{ name }}: {{ count }}</h2>
<p>Double: {{ counterStore.doubleCount }}</p>
<button @click="handleIncrement">Increment</button>
<input v-model="name" placeholder="Store name" />
</div>
</template>
TypeScriptには便利なユーティリティタイプが用意されており、これらを活用することでより柔軟な型定義が可能になります。
// よく使われるユーティリティタイプの例
// Partial: すべてのプロパティをオプショナルにする
interface User {
id: number
name: string
email: string
}
function updateUser(userId: number, userUpdate: Partial<User>) {
// userUpdateはUserの一部のプロパティだけを含む可能性がある
}
// Pick: 特定のプロパティだけを選択
type UserBasicInfo = Pick<User, 'id' | 'name'>
// UserBasicInfo = { id: number; name: string }
// Omit: 特定のプロパティを除外
type UserWithoutEmail = Omit<User, 'email'>
// UserWithoutEmail = { id: number; name: string }
// Record: キーと値の型を指定
type UserRoles = Record<string, boolean>
const roles: UserRoles = {
admin: true,
editor: false
}
TypeScriptでは、既存の型を拡張したり継承したりすることができます。
// インターフェースの拡張
interface BaseUser {
id: number
name: string
}
interface AdminUser extends BaseUser {
role: 'admin'
permissions: string[]
}
// 交差型(Intersection Types)
type Employee = {
employeeId: string
department: string
}
type EmployeeUser = BaseUser & Employee
// EmployeeUser = { id: number; name: string; employeeId: string; department: string }
Vue.jsでTypeScriptを使う上で役立つヒントをいくつか紹介します。
カスタム型定義ファイルを作成して、プロジェクト全体で使用できる型を定義できます。
// src/types/index.d.ts
export interface User {
id: number
name: string
email: string
role: 'admin' | 'user' | 'guest'
}
export interface Post {
id: number
title: string
content: string
authorId: number
created: Date
}
Viteの環境変数を型安全にするには、環境変数の宣言ファイルを作成します。
// env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
// その他の環境変数
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
コンポーネントの実装が正しいかを型チェックするために、TypeScriptの厳格モードを有効にすることをお勧めします。
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
TypeScriptはVue.jsプロジェクトに型安全性をもたらし、コードの品質と保守性を向上させます。この章では、Vue.jsプロジェクトでのTypeScriptの基本的な使い方を紹介しました。実際のプロジェクトでは、徐々に型定義を導入していくことで、段階的にTypeScriptの恩恵を受けることができます。Vue 3とComposition APIはTypeScriptとの相性が良いため、両者を組み合わせることで、より堅牢なアプリケーション開発が可能になります。