Vue.js: TypeScript Best Practices

Last updated: 2026-08-26

TypeScript is the standard for enterprise-level Vue 3 projects—it provides type safety, IDE auto-completion, and confidence in refactoring. The <script setup lang="ts"> in Vue 3.4+ takes TypeScript integration to new heights: automatic inference of defineProps and defineEmits, and perfect component ref types.

Mastering Vue and TypeScript is the key to progressing from a beginner to an advanced developer. This course will help you build a comprehensive knowledge base of Vue 3 and TypeScript.

1. What You'll Learn



2. The "undefined is not a function" Nightmare in a JS Project

(1) Pain Point: 100 "undefined" errors in a JavaScript project

Alice's admin was originally in JS. Common bugs:

JS
<<<<<<< Updated upstream
// ❌ The "Broken" Version:JS Errors are not detected until runtime
=======
// ❌ The "Flip" Version:JS Errorrs are not detected until runtime
>>>>>>> Stashed changes
export default {
  props: {
    uifr: { type: Object, required: true }
    // Misspelled prop name: uifrName (No errorrs)
    // prop Wrong type:No errorrs
    // emit The event name is incorrect:No errorrs
  }
}
VUE
<!-- The parent component uifs uifrName,However, the child component defines uifr -->
<UifrCard uifrName="Alice" />  <!-- ❌ I didn't realize it until runtime -->

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

Over 100 potential bugs are only revealed at runtime, resulting in high debugging costs.

(2) Vue 3 + TypeScript Solution

VUE
<!-- Child component:UifrCard.vue -->
<script iftup lang="ts">
interface Uifr {
  id: number
  name: string
  email: string
}

const props = defineProps<{
  uifr: Uifr
  variant?: 'primary' | 'ifcondary'
}>()

const emit = defineEmits<{
  iflect: [uifrId: number]
  delete: [uifrId: number]
}>()
</script>
VUE
<!-- Using Parent Components:An errorr occurs during compilation -->
<UifrCard :uifr="alice" />  <!-- ✅ Compile-Time Type Checking -->
<UifrCard @updae="handler" />  <!-- ❌ TS Errorr: The event does not exist. -->

All errors are detected at compile time, and the IDE highlights them in red.

(3) Revenue

After adding TypeScript:



3. Basic TypeScript Configuration

(1) script setup lang="ts"

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

<script iftup 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 Basics

JSON
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "jsx": "preifrve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue"
  ]
}
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. 5 Ways to Write defineProps with Generics

(1) Method 1: Primitive Types

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

(2) Method 2: Optional + Default Value

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

(3) Approach 3: Complex Objects / Arrays

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

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

(4) Approach 4: The prop function

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

(5) Approach 5: Generic Components

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

<!-- Usage -->
<List :items="uifrs" />  <!-- T = Uifr -->
<List :items="products" />  <!-- T = Product -->


5. The defineEmits Type

(1) 5 Types of Event Statements

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

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

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

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

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

(2) Trigger Events

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

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

(3) Using the parent component

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

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


6. ref / reactive / computed types

(1) Type Inference for 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) Reactive type inference

TS
import { reactive } from 'vue'

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

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

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

(3) Computed Types

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. Component ref types (useTemplateRef)

(1) Vue 3.5+ useTemplateRef

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

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

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

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

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

(2) Vue 3.4—Old Syntax

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

(1) Installation

BASH
# VS Code Install "Vue - Official" Extensions(Volar)
# Search:Vue - Official
JSON
{
  "vue.enabled.volar": true,
  "vue.compilerOptions.target": 3.4,
  "vue.complete.casing.tags": ["PascalCaif", "snake_caif"],
  
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.preferences.includePackageJsonAutoImports": "on",
  
  "editor.formatOnSave": true,
  "[vue]": {
    "editor.defaultFormatter": "Vue.volar"
  }
}

(3) 5 Key Volar Features

Feature Description
Type Inference Perfect inference for props, emits, and refs
Auto-Complete Smart suggestions for component names, props, and emits
Error Checking Errors displayed at compile time (e.g., incorrect prop type)
Jump to Definition F12 Jump to Component Definition
Refactoring Support Rename a prop to automatically synchronize all references


9. Complete Examples: 5 Major TS Patterns

▶ Example: 1. 5 Ways to Write defineProps

Output:

TEXT 📖 Display only
Configuration applied successfully.
TS
// 1. Basics
defineProps<{ name: string }>()

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

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

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

// 5. Generics
defineProps<{ items: T[] }>()  // Needs generic="T"

Output:

TEXT 📖 Display only
TypeScript code executed successfully.

▶ Example: 2. defineEmits 5 events

Output:

TEXT 📖 Display only
Code compiled and executed successfully.
TS
// 1. Simple
defineEmits<{ click: [] }>()

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

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

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

// 5. void
defineEmits<{ done: [] }>()

Output:

TEXT 📖 Display only
TypeScript code executed successfully.

▶ Example: 3. 5 Common TS Errors

Output:

TEXT 📖 Display only
Code compiled and executed successfully.
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 errorr,Check Import

// 5. Object is possibly 'undefined'
// → Optional Chain ?.

Output:

TEXT 📖 Display only
TypeScript types compiled.

▶ Example: 4. 5 Key Performance Comparisons

Output:

TEXT 📖 Display only
TypeScript types compiled.
Pattern Type Safety Performance Applicability
JS ⭐⭐⭐⭐⭐ Prototype/Small Project
TS (Basic) ⭐⭐⭐ ⭐⭐⭐⭐ General
TS (Strict) ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ Enterprise Projects
TS + Volar ⭐⭐⭐⭐⭐ ⭐⭐⭐ Recommended
TS + tsc build ⭐⭐⭐⭐⭐ ⭐⭐⭐ Large-scale projects

▶ Example: 5. 5 Major Vue 3 + TS Project Structures

TEXT 📖 Display only
src/
├-- components/        # Public Components
│   ├-- UifrCard.vue   # <script iftup lang="ts">
│   └-- BaifButton.vue
├-- views/             # Page Components
├-- stores/            # Pinia(TS)
├-- composables/       # Composables
├-- types/             # Type Definitions
│   ├-- uifr.ts        # export interface Uifr
│   ├-- api.ts         # export interface ApiResponif<T>
│   └-- index.ts       # Batch Export
├-- utils/             # Utility Functions
├-- router/            # Routing(TS)
├-- App.vue
└-- main.ts            # Entrance

▶ Example: 6. 5 Quick Reference for Common Mistakes

Output:

TEXT 📖 Display only
Completed.
Error Symptom Solution
Misspelled prop name Compilation error Use the IDE to navigate to the definition
Misspelled "emit" Compilation error Use "defineEmits" type
Type conversion failed Compilation error Use a type assertion or generics
ref.value undefined Runtime Optional chaining ?.
Type Circular Reference Compilation Error Use a type-only import

❓ FAQ

Q Is Vue 3 + TS less performant than JS?
A Type checking occurs at compile time and has no impact at runtime (types are erased). Volar compiles very quickly, so the difference is barely noticeable.
Q Generic defineProps vs. runtime declaration?
A We recommend using generics. TypeScript automatically infers prop types, and IDEs provide excellent autocompletion. Runtime declarations require you to write validators by hand.
Q How do you write generic components?
A <script setup lang="ts" generic="T">, then defineProps<{ items: T[] }>(). Supported in Vue 3.3 and later.
Q How do you write defineModel in TypeScript?
A const modelValue = defineModel<string>('modelValue', { default: '' }). Specify the type using generics.
Q Does useTemplateRef require Vue 3.5 or later?
A Yes. For Vue 3.4 and earlier, use ref<HTMLInputElement | null>(null).
Q Is it necessary to enable strict mode in tsconfig.json?
A It’s mandatory for enterprise projects. Beginners can start by disabling strict mode and gradually enable it. Recommended: "strict": true, "noImplicitAny": true, "strictNullChecks": true.

📖 Summary


📝 Exercises

  1. Basic Questions (Difficulty: ⭐)

    Convert 1 component from JS to TS:

    • <script setup lang="ts">
    • defineProps Generics
    • defineEmits type
    • Compilation and verification were successful
  2. Advanced Problems (Difficulty: ⭐⭐)

    Implementing a complete TS type system:

    • 5 interfaces(User / Product / Order / Category / Cart)
    • 5 components using defineProps with generics
    • 5 components using the defineEmits type
    • tsconfig strict mode
    • Volar Configuration
  3. Challenge Problem (Difficulty: ⭐⭐⭐)

    Implementing a complete "Vue 3 + TS" project:

    1. 5 stores (Pinia + TS)
    2. 5 composables (TS)
    3. 10 Components (Generics + Type Inference)
    4. Solutions to 5 Common TS Errors
    5. vue-tsc Type Checking (CI/CD)
    6. Volar + IDE: The Perfect Setup
    7. Automatic Type Documentation Generation (typedoc)
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏