404 Not Found

404 Not Found


nginx

Vue 3 + TypeScript のベストプラクティス:ジェネリック `defineProps`、コンポーネント型、Volar、および `tsconfig`

TypeScriptは、エンタープライズレベルのVue 3プロジェクトにおける標準です。型安全性、IDEの自動補完機能、そしてリファクタリングに対する安心感を提供します。Vue 3.4以降における<script setup lang="ts">は、TypeScriptとの統合を新たな高みへと引き上げます。具体的には、definePropsおよびdefineEmitsの自動推論、そしてコンポーネントの ref 型の完全なサポートが実現されています。

VueとTypeScriptを習得することは、初心者から上級開発者へとステップアップするための鍵となります。このコースでは、Vue 3とTypeScriptに関する包括的な知識を身につけることができます。

1. 学習内容


2. JSプロジェクトにおける「undefined is not a function」という悪夢

(1) 課題:JavaScript プロジェクトで発生した 100 件の「未定義」エラー

Aliceの管理機能は、もともとJSで実装されていました。よくあるバグ:

JS
// ❌ The "Flip" Version:JS Errors are not detected until runtime
export default {
  props: {
    user: { type: Object, required: true }
    // Misspelled prop name: userName (No errors)
    // prop Wrong type:No errors
    // emit The event name is incorrect:No errors
  }
}
VUE
<!-- The parent component uses userName,However, the child component defines user -->
<UserCard userName="Alice" />  <!-- ❌ I didn't realize it until runtime -->

<!-- emit The event name is incorrect -->
<Child @updae="handler" />  <!-- ❌ I didn't realize it until runtime -->

100件以上の潜在的なバグが実行時にのみ明らかになるため、デバッグに多大なコストがかかる。

(2) Vue 3 + TypeScript による解決策

VUE
<!-- Child component:UserCard.vue -->
<script setup lang="ts">
interface User {
  id: number
  name: string
  email: string
}

const props = defineProps<{
  user: User
  variant?: 'primary' | 'secondary'
}>()

const emit = defineEmits<{
  select: [userId: number]
  delete: [userId: number]
}>()
</script>
VUE
<!-- Using Parent Components:An error occurs during compilation -->
<UserCard :user="alice" />  <!-- ✅ Compile-Time Type Checking -->
<UserCard @updae="handler" />  <!-- ❌ TS Error: The event does not exist. -->

すべてのエラーはコンパイル時に検出され、IDEでは赤色で強調表示されます。

(3) 収益

TypeScript を追加した後:


3. TypeScriptの基本設定

(1) スクリプトの設定 lang="ts"

VUE
<template>
  <p>{{ count }}</p>
  <button @click="increment">+</button>
</template>

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

// ✅ TS Automatic Inference:Ref<number>
const count = ref(0)

// ✅ Parameters and Return Types
function increment(): void {
  count.value++
}
</script>

(2) tsconfig.json の基礎

JSON
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue"
  ]
}

(3) おすすめの構成トップ5

JSON
{
  "compilerOptions": {
    // 1. Strict Mode(Guaranteed to Open)
    "strict": true,
    
    // 2. No implicit any
    "noImplicitAny": true,
    
    // 3. Strict Null Checks
    "strictNullChecks": true,
    
    // 4. Strict Function Types
    "strictFunctionTypes": true,
    
    // 5. Strictly Bounded Calls
    "strictBindCallApply": true
  }
}

4. ジェネリクスを使って defineProps を書く 5 つの方法

(1) 方法 1:プリミティブ型

TS
const props = defineProps<{
  name: string
  age: number
  active: boolean
}>()

(2) 方法 2:オプション + デフォルト値

TS
// withDefaults Provide a default value
const props = withDefaults(defineProps<{
  name: string
  age?: number
  variant?: 'primary' | 'secondary'
}>(), {
  age: 18,
  variant: 'primary'
})

(3) アプローチ 3:複合オブジェクト/配列

TS
interface User {
  id: number
  name: string
  email: string
}

const props = defineProps<{
  user: User
  items: User[]
  config: Record<string, unknown>
}>()

(4) アプローチ 4:prop 関数

TS
const props = defineProps<{
  formatter: (value: number) => string
  onChange: (value: string) => void
}>()

(5) アプローチ 5:汎用コンポーネント

TS
// Generic Components:List<T> Can be specified item Type
<script setup lang="ts" generic="T extends { id: number }">
defineProps<{
  items: T[]
  selected?: T
}>()
</script>

<!-- Usage -->
<List :items="users" />  <!-- T = User -->
<List :items="products" />  <!-- T = Product -->

5. defineEmits タイプ

(1) イベント文の5つの種類

TS
// 1. Simple Events
const emit = defineEmits<{
  click: []
  submit: []
}>()

// 2. Single parameter
const emit = defineEmits<{
  select: [id: number]
  delete: [id: number]
}>()

// 3. Multi-parameter
const emit = defineEmits<{
  change: [id: number, oldValue: string, newValue: string]
}>()

// 4. Optional Parameters
const emit = defineEmits<{
  search: [query?: string]
  load: [id: number, options?: object]
}>()

// 5. void Return Value
const emit = defineEmits<{
  success: [data: object]
  error: [message: string]
}>()

(2) トリガーイベント

TS
const emit = defineEmits<{
  select: [id: number]
  delete: [id: number]
}>()

// ✅ TypeScript Check Parameter Types
emit('select', 123)  // ✅ OK
emit('select', 'abc')  // ❌ TS Error
emit('delete', 456)

(3) 親コンポーネントの使用

VUE
<template>
  <!-- ✅ TS Check Event Name -->
  <Child @select="handleSelect" @delete="handleDelete" />
  <!-- ❌ TS Error:The event does not exist. -->
  <!-- <Child @updae="handler" /> -->
</template>

<script setup lang="ts">
function handleSelect(id: number) {
  console.log('Selected:', id)
}
</script>

6. ref / reactive / computed 型

(1) ref の型推論

TS
import { ref } from 'vue'

// Automatically inferred as Ref<number>
const count = ref(0)
count.value = 1  // ✅

// Inferred as Ref<string>
const name = ref('Alice')

// Inferred as Ref<number | undefined>(It could be undefined)
const maybeNumber = ref<number>()
maybeNumber.value  // type: number | undefined

(2) リアクティブ型推論

TS
import { reactive } from 'vue'

// Automatic Inference
const state = reactive({
  count: 0,
  user: { name: 'Alice', age: 25 }
})

state.count  // type: number
state.user.name  // type: string

// Explicit Type
interface State {
  count: number
  items: string[]
}
const s = reactive<State>({
  count: 0,
  items: []
})

(3) 計算型

TS
import { ref, computed } from 'vue'

const count = ref(10)

// Automatic Inference:ComputedRef<number>
const double = computed(() => count.value * 2)

// Explicit Type
const formatted = computed<string>(() => `Count: ${count.value}`)

7. コンポーネントの参照型(useTemplateRef)

(1) バージョン 3.5 以降 useTemplateRef の表示

VUE
<template>
  <input ref="usernameInput">
  <MyChart ref="chartComponent" :data="chartData" />
</template>

<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'
import MyChart from './MyChart.vue'

// ✅ TS Automatic Inference:Ref<HTMLInputElement | null>
const inputRef = useTemplateRef<HTMLInputElement>('usernameInput')

// ✅ Component Instance Types
const chartRef = useTemplateRef<InstanceType<typeof MyChart>>('chartComponent')

onMounted(() => {
  inputRef.value?.focus()  // TS Auto-Complete
  chartRef.value?.refresh()
})
</script>

(2) Vue 3.4—旧構文

TS
// Old notation:Must be done manually ref<>
import { ref, onMounted } from 'vue'
import MyChart from './MyChart.vue'

const inputRef = ref<HTMLInputElement | null>(null)
const chartRef = ref<InstanceType<typeof MyChart> | null>(null)

8. Volar の詳細設定

(1) インストール

BASH
# VS Code Install "Vue - Official" Extensions(Volar)
# Search:Vue - Official

(2) settings.json の推奨設定

JSON
{
  "vue.enabled.volar": true,
  "vue.compilerOptions.target": 3.4,
  "vue.complete.casing.tags": ["PascalCase", "snake_case"],
  
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.preferences.includePackageJsonAutoImports": "on",
  
  "editor.formatOnSave": true,
  "[vue]": {
    "editor.defaultFormatter": "Vue.volar"
  }
}

(3) 掌側の5つの主要な特徴

機能 説明
型推論 プロップ、エミット、およびrefに対する完全推論
オートコンプリート コンポーネント名、プロパティ、エミットに対するスマートな候補表示
エラーチェック コンパイル時に表示されるエラー(例:propの型が正しくないなど)
定義へジャンプ F12 コンポーネントの定義へジャンプ
リファクタリングのサポート プロパティ名を変更すると、すべての参照が自動的に同期される

9. 完全な例:TSの5つの主要なパターン

(1) ▶ サンプル:. definePropsの書き方5選

TS
// 1. Basics
defineProps<{ name: string }>()

// 2. Optional+Default
withDefaults(defineProps<{ name?: string }>(), { name: 'Guest' })

// 3. Complex
defineProps<{ user: User; items: User[] }>()

// 4. Function
defineProps<{ onClick: () => void }>()

// 5. Generics
defineProps<{ items: T[] }>()  // Needs generic="T"
▶ 試してみよう

(2) ▶ サンプル:. defineEmits で 5 つのイベントを定義する

TS
// 1. Simple
defineEmits<{ click: [] }>()

// 2. Single Parameter
defineEmits<{ select: [id: number] }>()

// 3. More information
defineEmits<{ change: [old: string, new: string] }>()

// 4. Optional
defineEmits<{ search: [q?: string] }>()

// 5. void
defineEmits<{ done: [] }>()
▶ 試してみよう

(3) ▶ サンプル:. TSでよくある5つのエラー

TS
// 1. Property 'x' does not exist
// → Check Spelling,or add a type

// 2. Argument of type 'X' is not assignable
// → Type mismatch,Check Parameter Types

// 3. Type 'X' is not assignable to type 'Y | null'
// → Strict Null Checks, Add ! or ?

// 4. Cannot find module './X'
// → Path error,Check Import

// 5. Object is possibly 'undefined'
// → Optional Chain ?.
▶ 試してみよう

(4) ▶ サンプル:. 5つの主要なパフォーマンス比較

パターン 型安全性 パフォーマンス 適用性
JS ⭐⭐⭐⭐⭐ プロトタイプ/小規模プロジェクト
TS(基本) ⭐⭐⭐ ⭐⭐⭐⭐ 総合
TS (Strict) ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ エンタープライズプロジェクト
TS + Volar ⭐⭐⭐⭐⭐ ⭐⭐⭐ おすすめ
TS + tsc ビルド ⭐⭐⭐⭐⭐ ⭐⭐⭐ 大規模プロジェクト

(5) ▶ サンプル:. Vue 3 + TS プロジェクトの主な構造 5 選

TEXT
src/
├── components/        # Public Components
│   ├── UserCard.vue   # <script setup lang="ts">
│   └── BaseButton.vue
├── views/             # Page Components
├── stores/            # Pinia(TS)
├── composables/       # Composables
├── types/             # Type Definitions
│   ├── user.ts        # export interface User
│   ├── api.ts         # export interface ApiResponse<T>
│   └── index.ts       # Batch Export
├── utils/             # Utility Functions
├── router/            # Routing(TS)
├── App.vue
└── main.ts            # Entrance

(6) ▶ サンプル:. よくある間違いのクイックリファレンス

エラー 症状 解決策
プロパティ名のスペルミス コンパイルエラー IDE を使って定義箇所に移動
「emit」のスペルミス コンパイルエラー 「defineEmits」型を使用
型変換に失敗しました コンパイルエラー 型アサーションまたはジェネリックを使用してください
ref.value が未定義 実行時 オプションのチェーン ?.
循環参照 コンパイルエラー 型のみのインポートを使用してください

❓ よくある質問

Q Vue 3 + TS は JS よりもパフォーマンスが劣りますか?
A 型チェックはコンパイル時に実行され、実行時には影響しません(型情報は消去されます)。Volar のコンパイルは非常に高速であるため、その差はほとんど気になりません。
Q ジェネリック defineProps とランタイム宣言、どちらが良いですか?
A ジェネリックの使用をお勧めします。TypeScript はプロパティの型を自動的に推論し、IDE も優れたオートコンプリート機能を提供します。ランタイム宣言の場合、バリデータを手動で記述する必要があります。
Q 汎用コンポーネントはどのように記述しますか?
A <script setup lang="ts" generic="T">、次に defineProps<{ items: T[] }>() と記述します。Vue 3.3 以降でサポートされています。
Q TypeScriptでdefineModelはどのように記述しますか?
A const modelValue = defineModel<string>('modelValue', { default: '' })です。ジェネリックを使用して型を指定します。
Q useTemplateRef には Vue 3.5 以降が必要ですか?
A はい。Vue 3.4 以前の場合は、ref<HTMLInputElement | null>(null) を使用してください。
Q tsconfig.json で厳格モードを有効にする必要がありますか?
A エンタープライズプロジェクトでは必須です。初心者は、まず厳格モードを無効にして始め、徐々に有効にしていくことをお勧めします。推奨:"strict": true, "noImplicitAny": true, "strictNullChecks": true
Q Volcano / vue-tsc とは何ですか?
A vue-tsc は、Vue プロジェクト向けの TypeScript 型チェックツールです(Volar チームによって開発されました)。npx vue-tsc --noEmit 型チェックを実行します(JS を生成せず、型チェックのみを行います)。
Q Vue 3 と TypeScript を SSR で使用できますか?
A はい。Nuxt 3 には TypeScript サポートが組み込まれています。Pinia と Vue Router の両方が、ネイティブで TypeScript をサポートしています。

📖 まとめ


📝 練習問題

(1) 基礎問題(難易度:⭐)

1つのコンポーネントをJSからTSに変換する:

(2) 上級問題(難易度:⭐⭐)

完全なTS型システムの実装:

(3) チャレンジ問題(難易度:⭐⭐⭐)

「Vue 3 + TS」の完全なプロジェクトの実装:

  1. 5店舗(ピニア+TS)
  2. 5つのコンポーザブル(TS)
  3. 10の構成要素(ジェネリクス+型推論)
  4. TSでよく見られる5つのエラーの解決策
  5. vue-tsc による型チェック(CI/CD)
  6. Volar + IDE:理想的な構成
  7. 自動型ドキュメント生成 (typedoc)
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%