Vue.js: Componentes Dinâmicos e Assíncronos
Última atualização: 2026-08-26
Os componentes dinâmicos permitem que você renderize dinamicamente diferentes componentes usando uma única tag <component> — alternando entre abas, janelas modais e visualizações route com base nos dados. Os componentes assíncronos permitem que você carregue componentes sob demanda — carregando apenas o código necessário na primeira tela para melhorar o desempenho.
O componente experimental <Suspense> do Vue 3 foi projetado especificamente para lidar com o estado de carregamento de componentes assíncronos, eliminando a necessidade de escrever manualmente v-if="loading". Esta lição ajudará você a dominar três APIs principais.
1. O que você vai aprender
<component :is="...">Noções básicas sobre componentes dinâmicos- Duas maneiras de escrever
:is: sequência de caracteres / objeto componente defineAsyncComponentCarregamento assíncrono- Benefícios de desempenho da divisão de código (importação dinâmica)
<Suspense>Trabalho com componentes assíncronos- Componentes dinâmicos + cache com keep-alive
- 5 cenários reais e 3 antipadrões
2. O pesadelo das 5 instruções “v-if” em uma única aba
(1) Problema: 5 guias, 10 diretivas v-if aninhadas
Alice criou um painel de administração com abas:
<!-- ❌ The "Broken" Version: 5 Tabs with 5 v-if -->
<template>
<div class="tabs">
<button @click="currentTab = 'home'">Home</button>
<button @click="currentTab = 'profile'">Profile</button>
<button @click="currentTab = 'settings'">Settings</button>
<div v-if="currentTab === 'home'">
<HomeTab />
</div>
<div v-else-if="currentTab === 'profile'">
<ProfileTab />
</div>
<div v-else-if="currentTab === 'settings'">
<SettingsTab />
</div>
</div>
</template>
O gerente de produto Charlie:
“Alice, precisamos de mais 5 abas. 10 linhas de v-if-else são impossíveis de manter. Use um componente dinâmico.”
(2) Solução com componentes dinâmicos do Vue: 1 <component> para alternar entre 5 abas
<!-- ✅ Correct Version: 1 <component> Switch -->
<template>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.name"
:class="{ active: currentTab === tab.name }"
@click="currentTab = tab.name"
>
{{ tab.label }}
</button>
<!-- ✅ 1 <component> Dynamic Tag Switching -->
<component :is="currentTabComponent" />
</div>
</template>
<script setup>
import { ref, computed, defineAsyncComponent } from 'vue'
// Synchronization Components
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'
// Asynchronous Components(Laden on Demand)
const SettingsTab = defineAsyncComponent(() => import('./tabs/SettingsTab.vue'))
const tabs = [
{ name: 'home', label: 'Home' },
{ name: 'profile', label: 'Profile' },
{ name: 'settings', label: 'Settings' }
]
const currentTab = ref('home')
// Computed Properties:According to currentTab Determine which component to display
const currentTabComponent = computed(() => {
return { home: HomeTab, profile: ProfileTab, settings: SettingsTab }[currentTab.value]
})
</script>
(3) Receita
Após utilizar componentes dinâmicos:
- Tamanho do código: 10 linhas de v-if → 1 linha de
<component>(-90%) - Escalável: Para adicionar uma nova aba, basta 1 importação e 1 objeto de componente
- Desempenho na primeira tela: o carregamento assíncrono da guia “Configurações” economiza cerca de 50 KB
- Facilidade de manutenção: 5 guias gerenciadas em um único array
3. <component :is="..."> Noções básicas
(1) 5 maneiras de usar :is
<!-- Writing Style 1:Component Name String(Registered globally) -->
<component :is="'HomeTab'" />
<!-- Writing Style 2:Component Object(Most Commonly Used) -->
<component :is="HomeTab" />
<!-- Writing Style 3:Dynamically Calculated Properties -->
<component :is="currentTabComponent" />
<!-- Writing Style 4:Asynchronous Component Object -->
<component :is="asyncComponent" />
<!-- Writing Style 5:Inline Component Object -->
<component :is="{ template: '<div>Inline</div>' }" />
(2) Exemplo completo
<!-- TabContainer.vue -->
<template>
<div>
<button
v-for="tab in tabs"
:key="tab.name"
@click="currentTab = tab.name"
>
{{ tab.label }}
</button>
<!-- Dynamic Components -->
<component :is="currentTabComponent" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'
import SettingsTab from './tabs/SettingsTab.vue'
const tabs = [
{ name: 'home', label: 'Home' },
{ name: 'profile', label: 'Profile' },
{ name: 'settings', label: 'Settings' }
]
const currentTab = ref('home')
const currentTabComponent = computed(() => ({
home: HomeTab,
profile: ProfileTab,
settings: SettingsTab
}[currentTab.value]))
</script>
(3) 5 principais cenários de aplicação
| Cena | Componente dinâmico |
|---|---|
| Alternar entre abas | ✅ |
| Roteiro/Diálogo | ✅ |
| Formulários com várias etapas | ✅ |
| Visualização da rota | ✅ (componente interno <router-view>) |
| Troca de tema (layouts diferentes) | ✅ |
4. Componentes assíncronos defineAsyncComponent
(1) Por que os componentes assíncronos são necessários?
Os componentes padrão carregam todo o código imediatamente ao import. Componentes grandes (como editores de texto rico e bibliotecas de gráficos) não são necessários para a primeira tela, mas bloqueiam o carregamento. Os componentes assíncronos são carregados sob demanda, resultando em uma primeira tela mais rápida.
<script setup>
import { defineAsyncComponent } from 'vue'
import LoadingSpinner from './LoadingSpinner.vue'
// Asynchronous Components:Only when using ChartEditor Load its code only when needed
const ChartEditor = defineAsyncComponent({
// 1. Load Function
loader: () => import('./ChartEditor.vue'),
// 2. Display while loading
loadingComponent: LoadingSpinner,
// 3. Display when loading fails
errorComponent: ErrorMessage,
// 4. Load Delay(Prevent Flickering)
delay: 200
})
</script>
<template>
<ChartEditor v-if="showEditor" />
</template>
(2) Notação simplificada
<script setup>
import { defineAsyncComponent } from 'vue'
// ✅ Simplified Version:Only loader
const ChartEditor = defineAsyncComponent(() => import('./ChartEditor.vue'))
</script>
(3) 5 cenários principais para componentes assíncronos
| Cenário | Carregamento assíncrono |
|---|---|
| Editor de texto rico | ✅ |
| Biblioteca de gráficos (ECharts/D3) | ✅ |
| Formulários extensos (com mais de 100 campos) | ✅ |
| Janela modal | ✅ |
| Carregamento diferido de rotas | ✅ (Utilizado automaticamente pelo Vue Router) |
5. <Suspense> Como lidar com componentes assíncronos
(1) O que é suspense?
O Suspense é o coordenador de carregamento assíncrono de componentes do Vue 3 — ele gerencia automaticamente o carregamento e os estados de erro dos componentes assíncronos. Diga adeus à necessidade de escrever manualmente v-if="loading".
<template>
<!-- Suspense Automatically wait for asynchronous components to finish loading -->
<Suspense>
<!-- Asynchronous Components -->
<ChartEditor :data="chartData" />
<!-- Display while loading(Default Slot) -->
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>
(2) 2 vagas
<template>
<Suspense>
<!-- Default Slot:Asynchronous Content -->
<AsyncComponent />
<!-- Fallback Slot: Display while loading -->
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
(3) 5 principais vantagens
| Vantagem | Descrição |
|---|---|
| Simplicidade | Não é preciso codificar manualmente o estado de carregamento |
| Unificado | Processamento unificado de vários componentes assíncronos |
| Aninhamento | Suporta Suspense aninhado |
| Tratamento de erros | Funciona com onErrorCaptured |
| Compatível com SSR | Espera automática no lado do servidor |
6. Componentes dinâmicos + cache com keep-alive
(1) Comportamento padrão: a troca de componentes faz com que eles sejam destruídos e reconstruídos
<template>
<component :is="currentTabComponent" />
</template>
<!-- Switch Tab:
1. Legacy Components onBeforeUnmount
2. Legacy Components onUnmounted
3. New Component onBeforeMount
4. New Component onMounted
5. Data is reloaded every time you switch(Poor performance)
-->
(2) Cache Keep-Alive (para evitar a criação de duplicatas)
<template>
<keep-alive>
<component :is="currentTabComponent" />
</keep-alive>
</template>
<!-- Switch Tab:
1. Legacy Components onDeactivated(Destroy without stopping)
2. New Component onActivated(Restore from Cache)
3. Data Retention,Scroll Position Retention
-->
(3) Configurações-chave do keep-alive 3
<template>
<!-- include:Cache only the specified components -->
<keep-alive include="HomeTab,ProfileTab">
<component :is="currentTabComponent" />
</keep-alive>
<!-- exclude:Do not cache the specified component -->
<keep-alive exclude="SettingsTab">
<component :is="currentTabComponent" />
</keep-alive>
<!-- max: Maximum 5 Cached -->
<keep-alive :max="5">
<component :is="currentTabComponent" />
</keep-alive>
</template>
7. Exemplo completo: abas dinâmicas + carregamento assíncrono
▶ Exemplo: 1. Noções básicas sobre abas dinâmicas
<template>
<div>
<nav>
<button
v-for="tab in tabs"
:key="tab.name"
:class="{ active: currentTab === tab.name }"
@click="currentTab = tab.name"
>
{{ tab.label }}
</button>
</nav>
<component :is="currentTabComponent" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'
const tabs = [
{ name: 'home', label: 'Home' },
{ name: 'profile', label: 'Profile' },
{ name: 'settings', label: 'Settings' }
]
const currentTab = ref('home')
const currentTabComponent = computed(() => ({
home: HomeTab,
profile: ProfileTab,
settings: SettingsTab
}[currentTab.value]))
</script>
▶ Exemplo: 2. Componentes assíncronos + Suspense
<!-- App.vue -->
<template>
<button @click="show = !show">Toggle</button>
<Suspense v-if="show">
<!-- Asynchronous Components(Do not load on the first screen,Load only when clicked) -->
<HeavyChart :data="data" />
<template #fallback>
<div>Loading chart...</div>
</template>
</Suspense>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
const show = ref(false)
// ✅ Asynchronous Components:News import,Laden on Demand
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))
const data = ref([1, 2, 3, 4, 5])
</script>
▶ Exemplo: 3. Componentes dinâmicos + cache com keep-alive
<template>
<div>
<button v-for="tab in tabs" @click="currentTab = tab.name">
{{ tab.label }}
</button>
<!-- keep-alive Cache: Preserve data when switching -->
<keep-alive :max="3">
<component :is="currentTabComponent" />
</keep-alive>
</div>
</template>
▶ Exemplo: 4. Comparação de 5 maneiras de usar :is
| Sintaxe | Exemplo | Uso |
|---|---|---|
| String | <component :is="'HomeTab'"> |
Registro global (não recomendado) |
| Objeto componente | <component :is="HomeTab"> |
Importação estática (mais comum) |
| Objeto de componente assíncrono | <component :is="AsyncComponent"> |
Carregamento sob demanda |
| Propriedade calculada | <component :is="currentTabComponent"> |
Comutação dinâmica |
| Objeto embutido | <component :is="{ template: '...' }"> |
Cenário simples |
▶ Exemplo: 5. Referência rápida para 5 erros comuns
| Erro | Sintoma | Solução |
|---|---|---|
| :é uma string, mas não está registrada globalmente | Aviso: Componente desconhecido | Use um objeto de componente ou registre-o primeiro |
| Componentes assíncronos não têm alternativa | Tela em branco durante o carregamento | Envolver com Suspense |
| Alterar componentes para recarregar os dados | Baixo desempenho | Usar cache keep-alive |
| Erros não tratados em componentes assíncronos | Falhas silenciosas | Use com onErrorCaptured |
| Esqueci de adicionar :key ao componente dinâmico | Inconsistência de estado | Adicionar :key="tab.name" |
▶ Exemplo: 6. 5 Principais comparações de desempenho
| Modo | Primeira tela | Botão | Aplicável |
|---|---|---|---|
| Importação direta | Lenta | Atualizar | Widget |
| Importação dinâmica | Rápida | Lenta na primeira troca | Componentes grandes |
| Suspense | Rapidez | Fluidez | Prioridade à experiência do usuário |
| keep-alive | Rápido | 0 Atualização | Troca frequente |
| Assíncrono + keep-alive | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Melhores práticas |
❓ Perguntas Frequentes
P: Qual é a diferença entre componentes dinâmicos e v-if? R: Com o v-if, mesmo que haja 5 condições e 5 ramificações, o componente ainda é carregado na íntegra. Com os componentes dinâmicos, utiliza-se uma única tag
<component>, e o componente muda com base no atributo :is, tornando-o mais conciso. Os componentes assíncronos também podem ser carregados sob demanda.
P:
defineAsyncComponentprecisa estar no nível superior desetup? R: Sim.defineAsyncComponent(() => import('...'))deve ser chamado no nível superior de<script setup>(não dentro de uma função).
P: O Suspense é um recurso estável? R: Ele está estável na versão Vue 3.2 e posteriores. Você pode usá-lo com confiança, mas recomendamos testar exaustivamente o comportamento de fallback em ambientes de produção.
P: Os componentes dinâmicos podem passar props? R: Sim. Use
v-binddiretamente:<component :is="Comp" :prop1="x" :prop2="y" />.
P: Como faço para capturar erros em componentes assíncronos? R: Use a opção
errorComponent+ onErrorCaptured:{ loader, errorComponent: ErrorComp }. No Suspense, você pode envolver o código com onErrorCaptured.
P: Os dados dos componentes armazenados em cache pelo
keep-alivesão persistidos? R: Os dados são mantidos (não são destruídos) enquanto estiverem no cache. No entanto, atualizar a página ou mudar de rota limpará o cache. Use o Pinia para garantir a persistência.
P: Qual é a relação entre
defineAsyncComponente o carregamento diferido do Vue Router? R: O Vue Router usa internamentedefineAsyncComponentpara implementar o carregamento diferido das rotas. Essencialmente, trata-se da mesma coisa.
📖 Resumo
- Componentes dinâmicos:
<component :is="...">Alterne entre vários componentes com uma única guia - 5 maneiras de usar
:is: String / Objeto componente / Assíncrono / Propriedades calculadas / Em linha - Componente assíncrono
defineAsyncComponent: Carrega sob demanda para uma renderização mais rápida da primeira tela - Suspense: gerencia automaticamente o estado de carregamento dos componentes assíncronos
- Keep-Alive + Componentes dinâmicos: Armazenamento em cache para evitar a criação de duplicatas
- 5 cenários principais: abas / janelas modais / roteamento / formulários com várias etapas / temas
- Melhores práticas de desempenho: Assíncrono + Keep-Alive + Suspense
📝 Exercícios
-
Questões básicas (Dificuldade: ⭐)
Implemente um seletor de abas simples:
- 3 abas (Página inicial/Perfil/Configurações)
- Use
<component :is>para alternar dinamicamente - Saída no Switch: “Componente X criado/destruído”
-
Problemas avançados (Dificuldade: ⭐⭐)
Implementação da tecla Tab + carregamento assíncrono:
- 3 guias; a guia “Configurações” é carregada de forma assíncrona usando
defineAsyncComponent - Exibir o status de carregamento (símbolo “v” escrito à mão, se estiver carregando)
- Não atualiza a página ao voltar para a guia “Página inicial”
- 3 guias; a guia “Configurações” é carregada de forma assíncrona usando
-
Problema desafiador (Dificuldade: ⭐⭐⭐)
Implementação de um sistema completo, dinâmico, assíncrono e com cache:
- 5 abas, das quais pelo menos 2 são carregadas de forma assíncrona
- Envolva com
<Suspense> <keep-alive :max="3">Cache- Saída onActivated/onDeactivated ao alternar
- Exibir o ErrorComponent quando um componente assíncrono falhar
- Medir o tempo de carregamento da primeira tela (antes e depois da implementação assíncrona)
- TypeScript: Componentes dinâmicos fortemente tipados