# Composables Avançados no Vue 3: Padrões Reutilizáveis e Perguntas de Entrevista 2026 > Domine composables avançados do Vue 3 com padrões reutilizáveis, tipagem TypeScript e perguntas frequentes de entrevistas técnicas. Guia completo com exemplos práticos de composição, validação de formulários, injeção de dependências e testes unitários. - Published: 2026-05-12 - Updated: 2026-05-12 - Author: SharpSkill - Tags: vue, composables, composition-api, interview, deep-dive - Reading time: 11 min --- A Composition API do Vue 3 transformou a maneira como desenvolvedores organizam e compartilham lógica entre componentes. No centro dessa transformação estão os **composables** — funções que encapsulam estado reativo e lógica reutilizável. Para entrevistas técnicas em 2026, entender composables não é apenas desejável: é um requisito fundamental para qualquer vaga que envolva Vue.js. Este artigo explora padrões avançados de composables, desde a estrutura básica até composição entre composables, injeção de dependências com `provide/inject`, validação de formulários e testes unitários. Cada exemplo utiliza TypeScript e segue as convenções recomendadas pela documentação oficial do Vue 3. > **O que é um composable?** > > Um composable é uma função que utiliza a Composition API do Vue para encapsular e reutilizar lógica com estado (stateful logic). Por convenção, o nome sempre começa com `use` — como `useCounter`, `useFetch` ou `useAuth`. Composables podem conter refs, computeds, watchers e lifecycle hooks, retornando valores reativos que os componentes consomem diretamente. ## Anatomia de um Composable Bem Estruturado Antes de explorar padrões avançados, é essencial compreender a estrutura que torna um composable robusto, tipado e fácil de manter. Um bom composable segue princípios claros: aceita opções via parâmetro, define interfaces explícitas para entrada e saída, e retorna valores reativos de forma previsível. O exemplo a seguir demonstra um composable `useCounter` com limites configuráveis e um valor computado derivado: ```typescript // useCounter.ts import { ref, computed, type Ref } from 'vue' interface UseCounterOptions { min?: number max?: number initialValue?: number } interface UseCounterReturn { count: Ref doubled: Ref increment: () => void decrement: () => void reset: () => void } export function useCounter(options: UseCounterOptions = {}): UseCounterReturn { const { min = 0, max = Infinity, initialValue = 0 } = options const count = ref(initialValue) const doubled = computed(() => count.value * 2) function increment() { if (count.value < max) count.value++ } function decrement() { if (count.value > min) count.value-- } function reset() { count.value = initialValue } return { count, doubled, increment, decrement, reset } } ``` Vários aspectos tornam esse composable exemplar. A interface `UseCounterOptions` define parâmetros opcionais com valores padrão via desestruturação. A interface `UseCounterReturn` documenta exatamente o que o composable retorna, facilitando o autocompletar nas IDEs e a compreensão por outros desenvolvedores. O `computed` derivado (`doubled`) demonstra como composables podem expor valores calculados que se atualizam automaticamente quando o estado base muda. Em entrevistas, é comum que o entrevistador peça para implementar um composable como esse do zero, avaliando o domínio sobre `ref`, `computed` e a convenção de nomenclatura. ## Composables Assíncronos com Tratamento de Erros Requisições HTTP são um dos casos de uso mais frequentes para composables. Um composable assíncrono bem construído precisa gerenciar três estados fundamentais: os dados retornados, o estado de carregamento e possíveis erros. Além disso, deve lidar com cancelamento de requisições para evitar race conditions. ```typescript // useFetchData.ts import { ref, watchEffect, onUnmounted, toValue, type Ref, type MaybeRefOrGetter } from 'vue' interface UseFetchReturn { data: Ref error: Ref isLoading: Ref refresh: () => Promise } export function useFetchData( url: MaybeRefOrGetter ): UseFetchReturn { const data = ref(null) as Ref const error = ref(null) const isLoading = ref(false) let abortController: AbortController | null = null async function fetchData() { // Cancel any in-flight request abortController?.abort() abortController = new AbortController() isLoading.value = true error.value = null try { const response = await fetch(toValue(url), { signal: abortController.signal }) if (!response.ok) throw new Error(`HTTP ${response.status}`) data.value = await response.json() } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return error.value = err instanceof Error ? err.message : 'Unknown error' } finally { isLoading.value = false } } // Re-fetch when URL changes reactively watchEffect(() => { fetchData() }) onUnmounted(() => abortController?.abort()) return { data, error, isLoading, refresh: fetchData } } ``` Há vários detalhes importantes nessa implementação. O tipo `MaybeRefOrGetter` permite que a URL seja passada como string simples, como `ref` ou como getter, oferecendo flexibilidade máxima ao consumidor. O `AbortController` cancela requisições anteriores quando a URL muda, evitando que respostas desatualizadas sobrescrevam dados mais recentes. O `watchEffect` dispara automaticamente uma nova requisição sempre que qualquer dependência reativa (neste caso, a URL) muda. O cleanup via `onUnmounted` garante que requisições em andamento sejam canceladas quando o componente é destruído. Esse padrão é frequentemente solicitado em entrevistas técnicas, onde candidatos precisam demonstrar conhecimento sobre gerenciamento de estado assíncrono e prevenção de memory leaks. > **Cuidado com lifecycle hooks em composables** > > Composables que utilizam `onMounted`, `onUnmounted` ou outros lifecycle hooks só funcionam quando chamados dentro do `setup()` de um componente (ou dentro de outro composable chamado em `setup()`). Chamar esses composables fora do ciclo de vida do componente — como em um callback assíncrono atrasado — gera erros silenciosos. Sempre garanta que a chamada ocorra de forma síncrona durante a inicialização do componente. ## Composição de Composables Um dos padrões mais poderosos da Composition API é a capacidade de compor composables entre si. Em vez de criar funções monolíticas, composables menores e focados podem ser combinados para resolver problemas complexos. Esse padrão de composição é análogo ao princípio de responsabilidade única (SRP) no design orientado a objetos. O exemplo a seguir combina `useFetchData` com um `useDebouncedRef` para criar uma busca paginada reativa: ```typescript // usePaginatedSearch.ts import { ref, computed, watch, type Ref } from 'vue' import { useFetchData } from './useFetchData' import { useDebouncedRef } from './useDebouncedRef' interface UsePaginatedSearchReturn { query: Ref page: Ref results: Ref totalPages: Ref isLoading: Ref error: Ref nextPage: () => void prevPage: () => void } export function usePaginatedSearch( baseUrl: string, perPage = 20 ): UsePaginatedSearchReturn { const query = useDebouncedRef('', 300) const page = ref(1) const totalPages = ref(1) const apiUrl = computed( () => `${baseUrl}?q=${encodeURIComponent(query.value)}&page=${page.value}&limit=${perPage}` ) const { data, error, isLoading } = useFetchData<{ items: T[]; total: number }>(apiUrl) const results = computed(() => data.value?.items ?? null) watch(data, (response) => { if (response) { totalPages.value = Math.ceil(response.total / perPage) } }) // Reset to page 1 when query changes watch(query, () => { page.value = 1 }) function nextPage() { if (page.value < totalPages.value) page.value++ } function prevPage() { if (page.value > 1) page.value-- } return { query, page, results, totalPages, isLoading, error, nextPage, prevPage } } ``` Esse composable demonstra a composição em ação. O `useDebouncedRef` gerencia o debounce da digitação, evitando requisições excessivas enquanto o usuário digita. O `useFetchData` cuida de toda a lógica de fetch, cancelamento e estados de loading/erro. O `usePaginatedSearch` orquestra esses composables menores, adicionando lógica de paginação e reset automático ao trocar a query. Essa abordagem modular facilita os testes (cada composable pode ser testado isoladamente), melhora a legibilidade e permite que cada peça seja reutilizada em outros contextos da aplicação. ## Injeção de Dependências com provide/inject O mecanismo `provide/inject` do Vue permite que composables compartilhem estado entre componentes sem prop drilling. Esse padrão é particularmente útil para dados que precisam estar disponíveis em toda uma subárvore de componentes, como temas, configurações de internacionalização ou estado de autenticação. ```typescript // useTheme.ts import { provide, inject, ref, readonly, type InjectionKey, type Ref } from 'vue' type Theme = 'light' | 'dark' | 'system' interface ThemeContext { theme: Readonly> setTheme: (t: Theme) => void resolvedTheme: Readonly> } const ThemeKey: InjectionKey = Symbol('theme') export function provideTheme(initial: Theme = 'system') { const theme = ref(initial) const resolvedTheme = computed<'light' | 'dark'>(() => { if (theme.value !== 'system') return theme.value return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' }) function setTheme(t: Theme) { theme.value = t } const context: ThemeContext = { theme: readonly(theme), setTheme, resolvedTheme: readonly(resolvedTheme) } provide(ThemeKey, context) return context } export function useTheme(): ThemeContext { const context = inject(ThemeKey) if (!context) { throw new Error('useTheme() requires a parent component to call provideTheme()') } return context } ``` Vários padrões importantes aparecem nessa implementação. A `InjectionKey` tipada com generics garante type-safety total entre o provider e o consumer, eliminando a necessidade de type assertions manuais. O `readonly()` protege o estado de modificações diretas, forçando consumidores a usar `setTheme()` para alterar o tema. A separação entre `provideTheme` (chamado no componente raiz) e `useTheme` (chamado nos componentes filhos) segue a convenção do Vue para composables que dependem de contexto hierárquico. Esse padrão também é relevante para [perguntas de entrevista sobre Vue](/technologies/vue-nuxt/interview-questions/vue-composition-api), onde candidatos precisam explicar a diferença entre `provide/inject` e stores globais como Pinia, e quando cada abordagem é mais adequada. ## Composable de Validação de Formulários Formulários são onipresentes em aplicações web, e a validação é um dos maiores pontos de complexidade. Um composable de validação permite centralizar regras, gerenciar mensagens de erro e verificar o estado de validade do formulário de forma reativa. ```typescript // useFormValidation.ts import { reactive, computed, type UnwrapNestedRefs } from 'vue' type ValidationRule = (value: T) => string | true type FieldRules = { [K in keyof T]?: ValidationRule[] } interface UseFormReturn> { fields: UnwrapNestedRefs errors: Record isValid: Ref validate: () => boolean resetErrors: () => void } export function useFormValidation>( initialValues: T, rules: FieldRules ): UseFormReturn { const fields = reactive({ ...initialValues }) as UnwrapNestedRefs const errors = reactive( Object.keys(initialValues).reduce( (acc, key) => ({ ...acc, [key]: '' }), {} as Record ) ) function validate(): boolean { let valid = true for (const key of Object.keys(rules) as (keyof T)[]) { const fieldRules = rules[key] || [] errors[key] = '' as any for (const rule of fieldRules) { const result = rule(fields[key]) if (result !== true) { errors[key] = result as any valid = false break // Stop at first error per field } } } return valid } function resetErrors() { for (const key of Object.keys(errors)) { (errors as any)[key] = '' } } const isValid = computed(() => Object.values(errors).every((e) => e === '') ) return { fields, errors, isValid, validate, resetErrors } } ``` Esse composable utiliza `reactive` em vez de `ref` para gerenciar os campos e erros do formulário, o que permite acesso direto às propriedades sem `.value`. O sistema de regras é flexível: cada campo pode ter múltiplas funções de validação que retornam `true` para sucesso ou uma string de erro. A função `validate()` para no primeiro erro de cada campo (padrão *fail-fast*), enquanto o `computed` `isValid` reflete automaticamente o estado global de validade. Em entrevistas técnicas, a capacidade de projetar um sistema de validação genérico e tipado demonstra domínio avançado tanto de Vue quanto de TypeScript. ## Testando Composables Composables precisam ser testados fora de componentes reais. Como eles dependem do contexto de `setup()` do Vue, é necessário criar um wrapper que forneça esse contexto durante os testes. O padrão `withSetup` resolve esse problema de forma elegante: ```typescript // useCounter.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import { defineComponent, h } from 'vue' import { useCounter } from './useCounter' function withSetup(composable: () => T): { result: T; unmount: () => void } { let result!: T const wrapper = mount( defineComponent({ setup() { result = composable() return () => h('div') } }) ) return { result, unmount: () => wrapper.unmount() } } describe('useCounter', () => { it('initializes with default value', () => { const { result } = withSetup(() => useCounter()) expect(result.count.value).toBe(0) }) it('respects min and max boundaries', () => { const { result } = withSetup(() => useCounter({ min: 0, max: 3, initialValue: 3 }) ) result.increment() expect(result.count.value).toBe(3) // Capped at max result.count.value = 0 result.decrement() expect(result.count.value).toBe(0) // Capped at min }) it('computes doubled value reactively', () => { const { result } = withSetup(() => useCounter({ initialValue: 5 })) expect(result.doubled.value).toBe(10) result.increment() expect(result.doubled.value).toBe(12) }) }) ``` A função `withSetup` cria um componente mínimo que executa o composable dentro do contexto adequado. Esse padrão é amplamente utilizado pela comunidade Vue e pela própria equipe do Vue Test Utils. Os testes verificam o valor inicial, os limites de incremento/decremento e a reatividade do valor computado. Para composables que utilizam `onMounted` ou `onUnmounted`, o método `unmount()` retornado pelo wrapper permite simular a destruição do componente e verificar que os cleanups ocorrem corretamente. > **VueUse: a biblioteca de referência** > > O projeto VueUse (vueuse.org) oferece mais de 200 composables prontos para produção, cobrindo desde sensores do navegador até integrações com WebSocket e gerenciamento de estado. Estudar o código-fonte do VueUse é uma das melhores formas de aprender padrões avançados de composables. Muitos entrevistadores esperam que candidatos conheçam essa biblioteca e saibam quando utilizar composables prontos versus criar soluções customizadas. ## Perguntas Comuns em Entrevistas As perguntas a seguir aparecem frequentemente em entrevistas técnicas para vagas Vue.js em 2026. Cada resposta reflete o nível de profundidade esperado em processos seletivos para desenvolvedores pleno e sênior. **Qual é a diferença entre um composable e um mixin?** Mixins (da Options API) sofrem de conflitos de namespace, fontes implícitas de propriedades e dificuldade de tipagem com TypeScript. Composables resolvem todos esses problemas: não há conflitos porque as variáveis são explicitamente importadas, a rastreabilidade é total porque o consumidor vê exatamente o que o composable retorna, e o TypeScript consegue inferir tipos automaticamente. Mixins são considerados um anti-pattern no Vue 3. **Quando usar composables versus Pinia?** Composables são ideais para lógica reutilizável associada a componentes individuais ou subárvores de componentes (como validação de formulários, controles de UI ou requisições HTTP). Pinia é mais adequado para estado global da aplicação que precisa ser acessado por componentes não relacionados na árvore (como dados do usuário logado, carrinho de compras ou configurações da aplicação). Na prática, composables e Pinia frequentemente trabalham juntos — uma store Pinia pode usar composables internamente. **Como evitar memory leaks em composables?** Os principais cuidados são: cancelar requisições HTTP em andamento no `onUnmounted` (usando `AbortController`), remover event listeners registrados no `window` ou `document`, limpar intervalos e timeouts com `clearInterval`/`clearTimeout`, e descartar conexões WebSocket. O padrão recomendado é registrar o cleanup no mesmo local onde o efeito colateral é criado. **O que é `MaybeRefOrGetter` e por que importa?** É um tipo utilitário do Vue 3.3+ que aceita um valor como `Ref`, `() => T` ou `T` diretamente. Isso torna composables mais flexíveis, pois o consumidor pode passar valores de diferentes formas sem que o composable precise saber qual formato foi usado. A função `toValue()` unifica a leitura independentemente do formato recebido. **Como testar composables que dependem de lifecycle hooks?** Composables com `onMounted`, `onUnmounted` e similares precisam ser executados dentro de um contexto de `setup()`. O padrão `withSetup` — que cria um componente wrapper mínimo usando `mount()` do Vue Test Utils — é a abordagem recomendada. Para composables que dependem de `provide/inject`, o `mount()` aceita uma opção `global.provide` para injetar as dependências necessárias durante o teste. ## Conclusão Composables representam a abordagem moderna e recomendada para organizar lógica reutilizável no Vue 3. Dominar os padrões apresentados neste artigo prepara desenvolvedores tanto para projetos reais quanto para entrevistas técnicas cada vez mais exigentes. Os pontos centrais abordados incluem: - **Estrutura tipada**: interfaces explícitas para opções e retorno garantem autocompletar e previnem erros em tempo de compilação - **Composables assíncronos**: gerenciamento completo de loading, erro e cancelamento via `AbortController` - **Composição**: composables menores e focados combinados para resolver problemas complexos, seguindo o princípio de responsabilidade única - **provide/inject**: injeção de dependências tipada para compartilhar estado sem prop drilling - **Validação de formulários**: sistema genérico e extensível com regras customizáveis por campo - **Testes**: padrão `withSetup` para executar composables em contexto adequado com Vitest e Vue Test Utils - **VueUse**: biblioteca de referência com mais de 200 composables prontos para produção Para quem se prepara para entrevistas Vue.js em 2026, a recomendação é implementar cada um desses padrões do zero, compreendendo não apenas o *como*, mas o *porquê* de cada decisão de design. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns