Lesson 21 / 25

TypeScript With Vue

Typed props, emits and refs.

Type-based declarations

With <script setup lang="ts">, defineProps<{ ... }>() declares props from a TypeScript type, and withDefaults(defineProps<Props>(), { ... }) supplies defaults; Vue 3.5+ also allows defaults through reactive props destructuring. defineEmits<{ change: [id: number] }>() types event payloads (the tuple syntax needs Vue 3.3+). ref<User | null>(null) sets a ref's type when it cannot be inferred, template refs are typed with the element type, and computed infers its type from the getter. Use InjectionKey<T> for typed provide/inject. Because the TypeScript compiler cannot read .vue files, run vue-tsc --noEmit in CI for type checking. Check the docs for your version for the newest macros.

A typed component

Props, defaults, emits and refs.

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

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

const props = withDefaults(defineProps<{
  users: User[]
  pageSize?: number
}>(), { pageSize: 10 })

const emit = defineEmits<{
  select: [user: User]
  pageChange: [page: number]
}>()

const page = ref(1)
const selected = ref<User | null>(null)
const searchInput = ref<HTMLInputElement | null>(null)

const visible = computed(() =>
  props.users.slice((page.value - 1) * props.pageSize, page.value * props.pageSize))

function choose(user: User) {
  selected.value = user
  emit('select', user)        // emit('select', 42) would be a type error
}
</script>

<template>
  <input ref="searchInput" />
  <button v-for="u in visible" :key="u.id" @click="choose(u)">{{ u.name }}</button>
</template>

Type-check in CI

vite build strips types without checking them. Add vue-tsc --noEmit to your build or CI script so type errors fail the pipeline.

Quick check: Which tool type-checks .vue files from the command line?

  • tsc alone, with no plugins
  • vue-tsc
  • vite build
  • eslint --fix
Answer

vue-tsc — vue-tsc wraps TypeScript with Vue file support.