# 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 ` ``` 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 ``` > **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