Vue with TypeScript
Why TypeScript with Vue?
Vue 3 was rewritten in TypeScript and has first-class TypeScript support. Using TypeScript with Vue gives you:
- Type safety — catch errors at compile time, not runtime
- Better IDE support — autocomplete, refactoring, go-to-definition
- Self-documenting code — types serve as documentation
- Safer refactoring — TypeScript catches breaking changes
<!-- TypedComponent.vue -->
<template>
<div>
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<span :class="`badge-${user.role}`">{{ user.role }}</span>
<button @click="handleEdit">Edit</button>
<p>Count: {{ count }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { User, UserRole } from '@/types'
// Typed props with defineProps
interface Props {
user: User
editable?: boolean
maxItems?: number
}
const props = withDefaults(defineProps<Props>(), {
editable: true,
maxItems: 10,
})
// Typed emits with defineEmits
interface Emits {
(e: 'edit', user: User): void
(e: 'delete', id: number): void
(e: 'update:user', user: User): void
}
const emit = defineEmits<Emits>()
// Typed refs
const count = ref<number>(0)
const name = ref<string>('')
const users = ref<User[]>([])
const selectedRole = ref<UserRole | null>(null)
// Typed computed
const isAdmin = computed<boolean>(() => props.user.role === 'admin')
const displayName = computed<string>(() => `${props.user.name} (${props.user.role})`)
// Typed function
function handleEdit(): void {
emit('edit', props.user)
}
function updateUser(updates: Partial<User>): void {
emit('update:user', { ...props.user, ...updates })
}
// Typed template ref
import { useTemplateRef } from 'vue'
const inputRef = useTemplateRef<HTMLInputElement>('myInput')
function focusInput(): void {
inputRef.value?.focus()
}
</script>
// composables/useFetch.ts — typed composable
import { ref, watchEffect, toValue, type MaybeRefOrGetter } from 'vue'
interface FetchState<T> {
data: T | null
loading: boolean
error: string | null
}
export function useFetch<T>(url: MaybeRefOrGetter<string>) {
const state = ref<FetchState<T>>({
data: null,
loading: false,
error: null,
})
watchEffect(async () => {
const resolvedUrl = toValue(url)
if (!resolvedUrl) return
state.value.loading = true
state.value.error = null
try {
const res = await fetch(resolvedUrl)
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)
state.value.data = await res.json() as T
} catch (err) {
state.value.error = err instanceof Error ? err.message : 'Unknown error'
} finally {
state.value.loading = false
}
})
return state
}
// Usage with type inference:
// const usersState = useFetch<User[]>('/api/users')
// usersState.value.data // typed as User[] | null
// usersState.value.loading // boolean
// usersState.value.error // string | null
// composables/useCounter.ts
import { ref, computed } from 'vue'
export function useCounter(initialValue: number = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
const isPositive = computed(() => count.value > 0)
function increment(by: number = 1): void { count.value += by }
function decrement(by: number = 1): void { count.value -= by }
function reset(): void { count.value = initialValue }
return { count, doubled, isPositive, increment, decrement, reset }
}
// types/index.ts — shared type definitions
export type UserRole = 'admin' | 'user' | 'moderator' | 'guest'
export interface User {
id: number
name: string
email: string
role: UserRole
avatar?: string
createdAt: Date
isActive: boolean
}
export interface ApiResponse<T> {
data: T
message: string
success: boolean
pagination?: {
page: number
perPage: number
total: number
}
}
export interface FormField {
value: string
error: string | null
touched: boolean
}
export type FormState<T extends Record<string, unknown>> = {
[K in keyof T]: FormField
}
// Utility types
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
export type CreateUser = Optional<User, 'id' | 'createdAt' | 'isActive'>
export type UpdateUser = Partial<Omit<User, 'id'>>
// Component prop types
export interface TableColumn<T = unknown> {
key: keyof T
label: string
sortable?: boolean
width?: string
align?: 'left' | 'center' | 'right'
formatter?: (value: T[keyof T], row: T) => string
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.