Vue.js: TypeScript: Melhores Práticas

Última atualização: 2026-08-26

O TypeScript é o padrão para projetos Vue 3 de nível empresarial — ele oferece segurança de tipos, autocompletar no IDE e confiança na refatoração. O <script setup lang="ts"> no Vue 3.4+ leva a integração com o TypeScript a novos patamares: inferência automática de defineProps e defineEmits, além de tipos de referência de componentes perfeitos.

Dominar o Vue e o TypeScript é a chave para passar do nível iniciante para o avançado. Este curso vai te ajudar a construir uma base de conhecimento abrangente sobre o Vue 3 e o TypeScript.

1. O que você vai aprender


2. O pesadelo do “undefined is not a function” em um projeto em JavaScript

(1) Problema: 100 erros “indefinidos” em um projeto de JavaScript

O painel de administração do Alice era originalmente em JS. Erros comuns:

JS
// ❌ The "Broken" 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 -->

Mais de 100 possíveis erros são revelados apenas durante a execução, o que resulta em altos custos de depuração.

(2) Solução com 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. -->

Todos os erros são detectados na hora da compilação, e o IDE os destaca em vermelho.

(3) Receita

Depois de adicionar o TypeScript:


3. Configuração básica do TypeScript

(1) configuração do script 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) Noções básicas sobre o 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) As 5 principais configurações recomendadas

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 maneiras de escrever defineProps com genéricos

(1) Método 1: Tipos primitivos

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

(2) Método 2: Opcional + Valor padrão

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

(3) Abordagem 3: Objetos complexos / matrizes

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

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

(4) Abordagem 4: A função prop

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

(5) Abordagem 5: Componentes genéricos

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. O tipo defineEmits

(1) 5 tipos de declarações de evento

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) Eventos desencadeadores

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) Como usar o componente pai

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. Tipos ref / reativos / computados

(1) Inferência de tipos para 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) Inferência de tipos reativa

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) Tipos calculados

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. Tipos de referência de componentes (useTemplateRef)

(1) Visualização 3.5+ com 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 — Sintaxe antiga

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. Configuração avançada do Volar

(1) Instalação

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

(2) Arquivo settings.json recomendado

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 principais características volares

Recurso Descrição
Inferência de tipos Inferência perfeita para props, emits e refs
Autocompletar Sugestões inteligentes para nomes de componentes, propriedades e emissões
Verificação de erros Erros exibidos na hora da compilação (por exemplo, tipo de propriedade incorreto)
Ir para a definição F12: Ir para a definição do componente
Suporte à refatoração Renomeie uma propriedade para sincronizar automaticamente todas as referências

9. Complete Examples: 5 Major TS Patterns

▶ Exemplo: 1. 5 maneiras de escrever defineProps

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"
▶ Experimente

▶ Exemplo: 2. defineEmits emite 5 eventos

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: [] }>()
▶ Experimente

▶ Exemplo: 3. 5 erros comuns em TS

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 ?.
▶ Experimente

▶ Exemplo: 4. 5 Principais comparações de desempenho

Padrão Segurança de tipos Desempenho Aplicabilidade
JS ⭐⭐⭐⭐⭐ Protótipo/Pequeno projeto
TS (Básico) ⭐⭐⭐ ⭐⭐⭐⭐ Geral
TS (Rigoroso) ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ Projetos corporativos
TS + Volar ⭐⭐⭐⭐⭐ ⭐⭐⭐ Recomendado
TS + tsc build ⭐⭐⭐⭐⭐ ⭐⭐⭐ Projetos de grande escala

▶ Exemplo: 5. 5 Principais estruturas de projetos em Vue 3 + TS

TEXT 📖 Somente leitura
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

▶ Exemplo: 6. 5 Referência rápida para erros comuns

Erro Sintoma Solução
Nome da propriedade com erro ortográfico Erro de compilação Use o IDE para acessar a definição
Erro ortográfico na palavra “emit” Erro de compilação Use o tipo “defineEmits”
Falha na conversão de tipo Erro de compilação Use uma asserção de tipo ou genéricos
ref.value indefinido Tempo de execução Encadeamento opcional ?.
Referência circular de tipo Erro de compilação Use uma importação apenas de tipo

❓ Perguntas Frequentes

P: O Vue 3 + TS tem desempenho inferior ao JS? R: A verificação de tipos ocorre em tempo de compilação e não tem impacto em tempo de execução (os tipos são eliminados). O Volar compila muito rapidamente, então a diferença é quase imperceptível.

P: Genérico defineProps x declaração em tempo de execução? R: Recomendamos o uso de genéricos. O TypeScript infere automaticamente os tipos das propriedades, e os IDEs oferecem um excelente autocompletamento. As declarações em tempo de execução exigem que você escreva validadores manualmente.

P: Como se escrevem componentes genéricos? R: <script setup lang="ts" generic="T">, depois defineProps<{ items: T[] }>(). Compatível com o Vue 3.3 e versões posteriores.

P: Como se escreve defineModel em TypeScript? R: const modelValue = defineModel<string>('modelValue', { default: '' }). Especifique o tipo usando genéricos.

P: O useTemplateRef requer o Vue 3.5 ou uma versão posterior? R: Sim. Para o Vue 3.4 e versões anteriores, use o ref<HTMLInputElement | null>(null).

P: É necessário ativar o modo estrito no tsconfig.json? R: É obrigatório para projetos corporativos. Iniciantes podem começar desativando o modo estrito e ativá-lo gradualmente. Recomendado: "strict": true, "noImplicitAny": true, "strictNullChecks": true.

P: O que é o Volcano / vue-tsc? R: O vue-tsc é uma ferramenta de verificação de tipos em TypeScript para projetos Vue (desenvolvida pela equipe Volar). npx vue-tsc --noEmit Realiza verificações de tipos (não gera código JS, apenas verifica os tipos).

P: É possível usar o Vue 3 com TypeScript no SSR? R: Sim. O Nuxt 3 oferece suporte integrado ao TypeScript. Tanto o Pinia quanto o Vue Router oferecem suporte nativo ao TypeScript.


📖 Resumo


📝 Exercícios

  1. Questões básicas (Dificuldade: ⭐)

    Converter 1 componente de JS para TS:

    • <script setup lang="ts">
    • defineProps Genéricos
    • tipo defineEmits
    • A compilação e a verificação foram bem-sucedidas
  2. Problemas avançados (Dificuldade: ⭐⭐)

    Implementação de um sistema de tipos TS completo:

    • 5 interfaces (Usuário / Produto / Pedido / Categoria / Carrinho)
    • 5 componentes que utilizam defineProps com genéricos
    • 5 componentes do tipo defineEmits
    • modo estrito do tsconfig
    • Configuração do Volar
  3. Problema de desafio (Dificuldade: ⭐⭐⭐)

    Implementação de um projeto completo “Vue 3 + TS”:

    1. 5 lojas (Pinia + TS)
    2. 5 composables (TS)
    3. 10 Componentes (Genéricos + Inferência de Tipos)
    4. Soluções para 5 erros comuns do TS
    5. Verificação de tipos do vue-tsc (CI/CD)
    6. Volar + IDE: a configuração perfeita
    7. Geração automática de documentação de tipos (typedoc)
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%