# Vue 3 com TypeScript em 2026: props, emits e composables com tipos seguros
> Padrões de Vue 3 e TypeScript para 2026: defineProps, defineEmits, composables, defineModel e provide/inject tipados, com exemplos práticos e perguntas de entrevista.
- Published: 2026-07-07
- Updated: 2026-07-07
- Author: SharpSkill
- Tags: Vue 3, TypeScript, defineProps, Composables, Frontend
- Reading time: 8 min
---
O Vue 3 com TypeScript em 2026 oferece aos componentes verificação estática completa de tipos em props, eventos e lógica reutilizável. A sintaxe `
{{ props.name }}
{{ props.role }}
```
A prop `role` agora fica restrita a duas strings literais, de modo que passar `role="guest"` faz o build falhar. Essa é a principal razão para recorrer a `defineProps` com TypeScript: o [guia oficial de TypeScript do Vue](https://vuejs.org/guide/typescript/composition-api.html) trata a forma genérica como o padrão nos projetos que usam `
```
O Vue 3.5 estabilizou a **desestruturação reativa de props**, hoje o padrão mais conciso. Desestruturar o resultado de `defineProps` e atribuir um valor padrão na mesma instrução continua totalmente reativo: o compilador reescreve cada acesso de volta para `props.x` nos bastidores.
```vue
{{ label }}
```
> **A armadilha de reatividade com props desestruturadas**
>
> Props desestruturadas continuam reativas no template e em um `computed`, mas passar um valor desestruturado diretamente para `watch` ou para um composable o lê uma única vez e rompe o vínculo reativo. É preciso envolvê-lo em um getter — `watch(() => color, ...)` — ou convertê-lo com `toRef(props, 'color')` quando uma ref é necessária adiante.
Essa é uma armadilha comum em entrevistas: os candidatos presumem que a variável desestruturada é um valor simples, quando na verdade o compilador redirecionou cada leitura para `props.color`.
## Emits com tipos seguros usando defineEmits
Os eventos merecem o mesmo rigor que as props. A forma genérica de `defineEmits` descreve cada nome de evento e sua carga útil como uma tupla, dando ao componente pai o autocompletar e ao componente filho a garantia, em tempo de compilação, de que os argumentos corretos são emitidos.
```vue
```
Essa forma de tupla substituiu a antiga sintaxe por assinatura de chamada (`(e: 'search', q: string): void`) porque se lê melhor e suporta vários eventos sem sobrecargas. Quando um evento não carrega dados, uma tupla vazia `[]` documenta isso de forma explícita. Combinar emits tipados com props tipadas produz componentes cuja interface pública inteira é verificável antes da execução — a mesma disciplina abordada no [guia da API de composição do Vue](/blog/vue-nuxt/vue-3-composition-api-complete-guide).
## Tipando composables para lógica reutilizável
Composables são funções comuns, então seguem as regras ordinárias do TypeScript, mas algumas convenções os mantêm ergonômicos. Convém anotar explicitamente os tipos `Ref` quando a inferência não é óbvia e usar genéricos quando um composable envolve dados arbitrários, como a resposta de uma API.
```ts
// useFetch.ts
import { ref, type Ref } from 'vue'
interface UseFetchReturn {
data: Ref
error: Ref
loading: Ref
}
// Generic flows through to the caller's typed data
export function useFetch(url: string): UseFetchReturn {
const data = ref(null) as Ref
const error = ref(null)
const loading = ref(true)
fetch(url)
.then((r) => r.json())
.then((json: T) => { data.value = json })
.catch((e: Error) => { error.value = e })
.finally(() => { loading.value = false })
return { data, error, loading }
}
```
No ponto de chamada, o parâmetro genérico deixa `data` totalmente tipado sem nenhuma anotação adicional:
```ts
// UserList.vue (script setup)
interface User { id: number; name: string }
// data is Ref — inferred from the generic
const { data: users, loading } = useFetch('/api/users')
```
Anotar explicitamente o objeto de retorno (`UseFetchReturn`) vale essas poucas linhas extras: documenta o contrato, evita vazamentos acidentais de refs internas e dá aos consumidores um único tipo para importar. Para padrões de composables mais avançados, como sobrecargas de argumentos e limpeza ligada ao ciclo de vida, consulte o [guia avançado de composables do Vue](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns).
## Tipando defineModel para binding bidirecional
O `defineModel`, estável desde o Vue 3.4, condensa o antigo par de prop `modelValue` mais emit `update:modelValue` em uma única ref gravável. Seu parâmetro genérico tipa os dois sentidos do binding ao mesmo tempo.
```vue
```
Modelos nomeados — `defineModel('title')` — correspondem a `v-model:title` e recebem a mesma tipagem. Isso elimina uma categoria inteira de bugs de carga útil inconsistente que o padrão manual de prop e emit costumava esconder.
## Tipando refs de template e instâncias de componente
Acessar um nó do DOM ou um componente filho por meio de uma `ref` é onde o código Vue sem tipos mais frequentemente recai em `any`. A correção é parametrizar `useTemplateRef` (Vue 3.5+) ou a própria `ref` com o tipo do elemento, para que o acesso às propriedades seja verificado contra a interface real do DOM.
```vue
```
Para uma referência a um componente filho, `InstanceType` extrai o tipo público do componente, expondo tudo o que o filho declarou por meio de `defineExpose`. Assim, as chamadas de método do pai para o filho permanecem totalmente verificadas em vez de adivinhadas.
```vue
```
## provide e inject com tipos seguros usando InjectionKey
A injeção de dependências através da árvore de componentes perde a informação de tipo a menos que a chave a carregue. `InjectionKey` é um símbolo tipado que vincula o tipo de um valor à sua chave, de modo que `provide` e `inject` permanecem sincronizados sem cast manual.
```ts
// theme-key.ts
import type { InjectionKey, Ref } from 'vue'
export interface ThemeContext {
mode: Ref<'light' | 'dark'>
toggle: () => void
}
// The key permanently associates the ThemeContext type with this symbol
export const ThemeKey: InjectionKey = Symbol('theme')
```
```ts
// provider (script setup) — value must match ThemeContext or it fails to compile
provide(ThemeKey, { mode, toggle })
// consumer — theme is inferred as ThemeContext | undefined
const theme = inject(ThemeKey)
theme?.toggle()
```
Fornecer um valor cujo formato não corresponde a `ThemeContext` provoca um erro de compilação no ponto de injeção, capturando qualquer desvio entre componentes distantes antes de chegar à produção.
## Perguntas comuns de entrevista sobre Vue e TypeScript
Os entrevistadores investigam se o candidato compreende a fronteira entre os tipos de compilação e o comportamento em tempo de execução. Algumas perguntas recorrentes e suas respostas precisas:
- **Por que preferir `defineProps()` à forma objeto de execução?** As props genéricas expressam tipos união, assinaturas de função e formatos aninhados que as declarações de execução não conseguem capturar, e removem a duplicação entre a definição de tipo e a de execução.
- **As props desestruturadas são reativas?** Sim, desde o Vue 3.5+, porque o compilador reescreve o acesso para `props.x`. Mas um *valor* desestruturado passado para `watch` ou para um composable é lido uma única vez — use um getter ou `toRef`.
- **Como tipar um evento emitido sem carga útil?** Com uma tupla vazia: `defineEmits<{ close: [] }>()`.
- **O que o `defineModel` substitui?** O par de prop `modelValue` e emit `update:modelValue`, unificado em uma única ref gravável tipada.
Praticar contra um banco de perguntas real afia os reflexos que as entrevistas recompensam — o [módulo de entrevista sobre composables do Vue](/technologies/vue-nuxt/interview-questions/vue-composables) treina exatamente esses padrões. A ferramentaria também importa: rode `vue-tsc` na CI para que os erros de tipo bloqueiem os merges, e apoie-se no [manual do TypeScript](https://www.typescriptlang.org/docs/handbook/2/generics.html) quando os genéricos de composables entram em cena. A experiência no editor é impulsionada pela [ferramentaria oficial Volar do Vue](https://github.com/vuejs/language-tools), que lê essas macros para sinalizar os erros em linha.
## Conclusão
- Usar a forma genérica `defineProps()` para expressar tipos união, campos opcionais e props callback que as declarações de execução não conseguem capturar.
- Preferir a desestruturação reativa de props com valores padrão em linha a partir do Vue 3.5+, e recorrer a `withDefaults` apenas quando um objeto de valores padrão compartilhado for mais claro.
- Tipar os eventos com a forma de tupla de `defineEmits` para que as cargas úteis sejam verificadas em tempo de compilação tanto no filho quanto no pai.
- Anotar explicitamente os tipos de retorno dos composables e usar genéricos para encaminhar de ponta a ponta os tipos de dados fornecidos por quem chama.
- Adotar `defineModel()` para o binding bidirecional a fim de condensar o boilerplate de prop mais emit em uma única ref tipada.
- Rodar `vue-tsc` na integração contínua para que as regressões de tipo façam o build falhar em vez de chegar à produção.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/pt/blog/vue-nuxt/vue-3-typescript-type-safe-props-emits-composables