[Easy] Novos Recursos do Vue3
1. What are the new features in Vue 3?
Quais são os novos recursos do Vue 3?
O Vue 3 introduziu muitos novos recursos é melhorias, incluindo principalmente:
Principais Novos Recursos
- Composition API: Nova forma de escrever componentes
- Teleport: Renderizar componentes em outras posições do DOM
- Fragment: Componentes podem ter múltiplos nós raiz
- Suspense: Tratar carregamento de componentes assíncronos
- Múltiplos v-model: Suporte a múltiplos v-model
- Melhor suporte a TypeScript
- Otimização de performance: Bundle menor, renderização mais rápida
2. Teleport
O que é Teleport?
Definição: Teleport permite renderizar o conteúdo de um componente em outra posição da árvore DOM, sem alterar a estrutura lógica do componente.
Cenários de Uso
Cenários comuns: Modal, Tooltip, Notification e outros componentes que precisam ser renderizados no body
Clique para expandir o exemplo de Teleport
<template>
<div>
<button @click="showModal = true">Abrir Modal</button>
<!-- Usar Teleport para renderizar o Modal no body -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<div class="modal-content">
<h2>Título do Modal</h2>
<p>Conteúdo do Modal</p>
<button @click="showModal = false">Fechar</button>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue';
const showModal = ref(false);
</script>
Vantagens
- Resolve problemas de z-index: Modal renderizado no body, não é afetado pelos estilos do componente pai
- Mantém a estrutura lógica: A lógica do componente permanece no local original, apenas a posição do DOM é diferente
- Melhor manutenibilidade: Código relacionado ao Modal centralizado no componente
3. Fragment (Múltiplos Nós Raiz)
O que é Fragment?
Definição: O Vue 3 permite que componentes tenham múltiplos nós raiz, sem necessidade de envolve-los em um único elemento. Este é um Fragment implícito, não necessitando de uma tag <Fragment> como no React.
Vue 2 vs Vue 3
Vue 2: Obrigatório ter um único nó raiz
<!-- Vue 2: obrigatório envolver em um único elemento -->
<template>
<div>
<h1>Título</h1>
<p>Conteúdo</p>
</div>
</template>
Vue 3: Pode ter múltiplos nós raiz
<!-- Vue 3: pode ter múltiplos nós raiz -->
<template>
<h1>Título</h1>
<p>Conteúdo</p>
</template>
Por que precisamos de Fragment?
No Vue 2, componentes deviam ter um único nó raiz, o que frequentemente obrigava os desenvolvedores a adicionar elementos wrapper extras (como <div>), que:
- Quebram HTML semântico: Adicionam elementos wrapper sem significado
- Aumentam níveis do DOM: Afetam seletores de estilo e performance
- Dificultam controle de estilos: Necessitam tratar estilos do elemento wrapper extra
Cenários de Uso
Cenário 1: Estrutura HTML Semântica
<template>
<!-- Sem necessidade de elemento wrapper extra -->
<header>
<h1>Título do Site</h1>
</header>
<main>
<p>Conteúdo Principal</p>
</main>
<footer>
<p>Rodape</p>
</footer>
</template>
Cenário 2: Componente de Item de Lista
<!-- ListItem.vue -->
<template>
<li class="item-title">{{ title }}</li>
<li class="item-description">{{ description }}</li>
</template>
<script setup>
defineProps({
title: String,
description: String,
});
</script>
Cenário 3: Renderização Condicional de Múltiplos Elementos
<template>
<div v-if="showHeader" class="header">Título</div>
<div v-if="showContent" class="content">Conteúdo</div>
<div v-if="showFooter" class="footer">Rodape</div>
</template>
Herança de Atributos (Attribute Inheritance)
Quando um componente tem múltiplos nós raiz, o comportamento de herança de atributos é diferente.
Nó raiz único: Atributos são herdados automaticamente pelo elemento raiz
<!-- Componente pai -->
<MyComponent class="custom-class" id="my-id" />
<!-- Componente filho (raiz único) -->
<template>
<div>Conteúdo</div>
</template>
<!-- Resultado renderizado -->
<div class="custom-class" id="my-id">Conteúdo</div>
Múltiplos nós raiz: Atributos não são herdados automaticamente, necessita tratamento manual
<!-- Componente pai -->
<MyComponent class="custom-class" id="my-id" />
<!-- Componente filho (múltiplos raiz) -->
<template>
<div>Primeiro raiz</div>
<div>Segundo raiz</div>
</template>
<!-- Resultado renderizado: atributos não são herdados automaticamente -->
<div>Primeiro raiz</div>
<div>Segundo raiz</div>
Solução: Usar $attrs para vincular atributos manualmente
<!-- Componente filho -->
<template>
<div v-bind="$attrs">Primeiro raiz</div>
<div>Segundo raiz</div>
</template>
<!-- Resultado renderizado -->
<div class="custom-class" id="my-id">Primeiro raiz</div>
<div>Segundo raiz</div>
Usar inheritAttrs: false para controlar o comportamento de herança:
<script setup>
defineOptions({
inheritAttrs: false, // Desabilitar herança automática
});
</script>
<template>
<div v-bind="$attrs">Primeiro raiz</div>
<div>Segundo raiz</div>
</template>
Fragment vs React Fragment
| Característica | Vue 3 Fragment | React Fragment |
|---|---|---|
| Sintaxe | Implícito (sem tag) | Explícito (necessita <Fragment> ou <>) |
| Atributo Key | Não necessário | Quando necessário usa <Fragment key={...}> |
| Herança de atributos | Necessita tratamento manual | Não suporta atributos |
Vue 3:
<!-- Vue 3: Fragment implícito -->
<template>
<h1>Título</h1>
<p>Conteúdo</p>
</template>
React:
// React: Fragment explícito
function Component() {
return (
<>
<h1>Título</h1>
<p>Conteúdo</p>
</>
);
}
Observacoes
- Herança de atributos: Com múltiplos nós raiz, atributos não são herdados automaticamente, necessita vincular manualmente com
$attrs - Escopo de estilos: Com múltiplos nós raiz, estilos
scopedsão aplicados a todos os nós raiz - Wrapper lógico: Se logicamente precisa de wrapper, ainda deve usar nó raiz único
<!-- ✅ Boa prática: logicamente necessita wrapper -->
<template>
<div class="card">
<h2>Título</h2>
<p>Conteúdo</p>
</div>
</template>
<!-- ⚠️ Evitar: múltiplos raiz sem necessidade -->
<template>
<h2>Título</h2>
<p>Conteúdo</p>
<!-- Se estes elementos logicamente devem ser um grupo, devem ser envolvidos -->
</template>
4. Suspense
O que é Suspense?
Definição: Suspense é um componente built-in usado para tratar o estado de carregamento de componentes assíncronos.
Uso Básico
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Carregando...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
);
</script>
Cenários de Uso
-
Carregamento de componentes assíncronos
<Suspense>
<AsyncUserProfile :userId="userId" />
<template #fallback>
<UserProfileSkeleton />
</template>
</Suspense> -
Carregamento assíncrono de dados
<script setup>
const data = await fetchData(); // Usar await no setup
</script>
5. Multiple v-model
Múltiplos v-model
Definição: O Vue 3 permite que componentes usem múltiplos v-model, cada um correspondendo a um prop diferente.
Vue 2 vs Vue 3
Vue 2: Apenas um v-model
<!-- Vue 2: apenas um v-model -->
<CustomInput v-model="value" />
Vue 3: Pode ter múltiplos v-model
<!-- Vue 3: pode ter múltiplos v-model -->
<CustomForm
v-model:username="username"
v-model:email="email"
v-model:password="password"
/>
Exemplo de Implementação
<!-- CustomForm.vue -->
<template>
<div>
<input
:value="username"
@input="$emit('update:username', $event.target.value)"
/>
<input :value="email" @input="$emit('update:email', $event.target.value)" />
<input
:value="password"
@input="$emit('update:password', $event.target.value)"
/>
</div>
</template>
<script setup>
defineProps(['username', 'email', 'password']);
defineEmits(['update:username', 'update:email', 'update:password']);
</script>
6. Common Interview Questions
Perguntas Comuns de Entrevista
Pergunta 1: Cenários de Uso do Teleport
Explique quando devemos usar Teleport.
Clique para ver a resposta
Cenários para usar Teleport:
-
Modal (Dialogo)
<Teleport to="body">
<Modal v-if="showModal" />
</Teleport>- Resolve problemas de z-index
- Não é afetado pelos estilos do componente pai
-
Tooltip
<Teleport to="body">
<Tooltip v-if="showTooltip" />
</Teleport>- Evita ser oculto pelo overflow do componente pai
-
Notification
<Teleport to="#notifications">
<Notification v-for="msg in messages" :key="msg.id" />
</Teleport>- Gerenciamento unificado da posição das notificações
Quando não usar Teleport:
- Conteúdo geral não necessita
- Componentes que não precisam de posição especial no DOM
Pergunta 2: Vantagens do Fragment
Explique as vantagens de permitir múltiplos nós raiz no Vue 3.
Clique para ver a resposta
Vantagens:
-
Reduz elementos DOM desnecessários
<!-- Vue 2: necessita div extra -->
<template>
<div>
<header>...</header>
<main>...</main>
</div>
</template>
<!-- Vue 3: sem necessidade de elemento extra -->
<template>
<header>...</header>
<main>...</main>
</template> -
Melhor HTML semântico
- Não necessita adicionar elementos wrapper sem significado por limitação do Vue
- Mantém a semântica da estrutura HTML
-
Controle de estilos mais flexível
- Não necessita tratar estilos de elementos wrapper extras
- Reduz complexidade dos seletores CSS
-
Reduz níveis do DOM
- Árvore DOM mais rasa, melhor performance
- Reduz custo de renderização do navegador
-
Melhor manutenibilidade
- Código mais limpo, sem necessidade de elementos wrapper extras
- Estrutura do componente mais clara
Pergunta 3: Problema de Herança de Atributos do Fragment
Explique o comportamento de herança de atributos quando um componente tem múltiplos nós raiz. Como resolver?
Clique para ver a resposta
Problema:
Quando um componente tem múltiplos nós raiz, atributos passados pelo componente pai (como class, id, etc.) não são herdados automaticamente por nenhum nó raiz.
Exemplo:
<!-- Componente pai -->
<MyComponent class="custom-class" id="my-id" />
<!-- Componente filho (múltiplos raiz) -->
<template>
<div>Primeiro raiz</div>
<div>Segundo raiz</div>
</template>
<!-- Resultado renderizado: atributos não herdados automaticamente -->
<div>Primeiro raiz</div>
<div>Segundo raiz</div>
Soluções:
- Usar
$attrspara vincular atributos manualmente
<!-- Componente filho -->
<template>
<div v-bind="$attrs">Primeiro raiz</div>
<div>Segundo raiz</div>
</template>
<!-- Resultado renderizado -->
<div class="custom-class" id="my-id">Primeiro raiz</div>
<div>Segundo raiz</div>
- Usar
inheritAttrs: falsepara controlar comportamento de herança
<script setup>
defineOptions({
inheritAttrs: false, // Desabilitar herança automática
});
</script>
<template>
<div v-bind="$attrs">Primeiro raiz</div>
<div>Segundo raiz</div>
</template>
- Vincular atributos específicos seletivamente
<template>
<div :class="$attrs.class">Primeiro raiz</div>
<div :id="$attrs.id">Segundo raiz</div>
</template>
Pontos-chave:
- Nó raiz único: atributos são herdados automaticamente
- Múltiplos nós raiz: atributos não são herdados automaticamente, necessita tratamento manual
- Usar
$attrspode acessar todos os atributos não definidos emprops
Pergunta 4: Fragment vs React Fragment
Compare as diferenças entre Vue 3 Fragment e React Fragment.
Clique para ver a resposta
Principais diferenças:
| Característica | Vue 3 Fragment | React Fragment |
|---|---|---|
| Sintaxe | Implícito (sem tag) | Explícito (necessita <Fragment> ou <>) |
| Atributo Key | Não necessário | Quando necessário usa <Fragment key={...}> |
| Herança de atributos | Necessita tratamento manual ($attrs) | Não suporta atributos |
Vue 3:
<!-- Vue 3: Fragment implícito, escreva múltiplos nós raiz diretamente -->
<template>
<h1>Título</h1>
<p>Conteúdo</p>
</template>
React:
// React: Fragment explícito, necessita usar tags
function Component() {
return (
<>
<h1>Título</h1>
<p>Conteúdo</p>
</>
);
}
// Ou usando Fragment
import { Fragment } from 'react';
function Component() {
return (
<Fragment>
<h1>Título</h1>
<p>Conteúdo</p>
</Fragment>
);
}
Comparação de vantagens:
- Vue 3: Sintaxe mais concisa, sem tags extras
- React: Mais explícito, pode adicionar atributo key
Pergunta 5: Uso do Suspense
Implemente um exemplo usando Suspense para carregar um componente assíncrono.
Clique para ver a resposta
<template>
<Suspense>
<template #default>
<AsyncUserProfile :userId="userId" />
</template>
<template #fallback>
<div class="loading">
<Spinner />
<p>Carregando dados do usuario...</p>
</div>
</template>
</Suspense>
</template>
<script setup>
import { ref } from 'vue';
import { defineAsyncComponent } from 'vue';
import Spinner from './Spinner.vue';
const userId = ref(1);
// Definir componente assíncrono
const AsyncUserProfile = defineAsyncComponent(() =>
import('./UserProfile.vue')
);
</script>
Uso avancado: Tratamento de erros
<template>
<Suspense @resolve="onResolve" @reject="onReject">
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Carregando...</div>
</template>
</Suspense>
</template>
<script setup>
const onResolve = () => {
console.log('Componente carregado com sucesso');
};
const onReject = (error) => {
console.error('Falha ao carregar componente:', error);
};
</script>
7. Best Practices
Melhores Práticas
Práticas Recomendadas
<!-- 1. Modal usando Teleport -->
<Teleport to="body">
<Modal v-if="showModal" />
</Teleport>
<!-- 2. Múltiplos nós raiz mantendo semântica -->
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
<!-- 3. Componentes assíncronos usando Suspense -->
<Suspense>
<AsyncComponent />
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
<!-- 4. Múltiplos v-model com nomes explicitos -->
<CustomForm v-model:username="username" v-model:email="email" />
Práticas a Evitar
<!-- 1. Não usar Teleport excessivamente -->
<Teleport to="body">
<div>Conteúdo geral</div> <!-- ❌ Não necessário -->
</Teleport>
<!-- 2. Não quebrar a estrutura para ter múltiplos raiz -->
<template>
<h1>Título</h1>
<p>Conteúdo</p>
<!-- ⚠️ Se logicamente precisa de wrapper, use nó raiz único -->
</template>
<!-- 3. Não ignorar tratamento de erros do Suspense -->
<Suspense>
<AsyncComponent />
<!-- ⚠️ Deve tratar situações de falha de carregamento -->
</Suspense>
8. Interview Summary
Resumo para Entrevista
Memorização Rápida
Principais novos recursos do Vue 3:
- Composition API: Nova forma de escrever componentes
- Teleport: Renderizar componentes em outras posições do DOM
- Fragment: Suporte a múltiplos nós raiz
- Suspense: Tratar carregamento de componentes assíncronos
- Múltiplos v-model: Suporte a múltiplas ligações v-model
Cenários de uso:
- Modal/Tooltip →
Teleport - HTML semântico →
Fragment - Componentes assíncronos →
Suspense - Componentes de formulário → Múltiplos
v-model
Exemplo de Resposta para Entrevista
P: Quais são os principais novos recursos do Vue 3?
"O Vue 3 introduziu muitos novos recursos, incluindo principalmente: 1) Composition API, oferecendo nova forma de escrever componentes com melhor organização lógica e reutilização de código; 2) Teleport, permitindo renderizar conteúdo de componentes em outras posições da árvore DOM, comumente usado para Modal, Tooltip, etc.; 3) Fragment, componentes podem ter múltiplos nós raiz sem necessidade de elementos wrapper extras; 4) Suspense, trata o estado de carregamento de componentes assíncronos; 5) Múltiplos v-model, suporte a múltiplas ligações v-model em componentes; 6) Melhor suporte a TypeScript e otimização de performance. Esses novos recursos tornam o Vue 3 mais poderoso e flexível, mantendo a compatibilidade."
P: Quais são os cenários de uso do Teleport?
"O Teleport é usado principalmente em cenários onde o componente precisa ser renderizado em outra posição da árvore DOM. Cenários comuns incluem: 1) Modal (dialogos), renderizado no body para evitar problemas de z-index; 2) Tooltip, evitando ser oculto pelo overflow do componente pai; 3) Notification, gerenciamento unificado da posição. A vantagem do Teleport e manter a estrutura lógica do componente inalterada, apenas alterando a posição de renderização do DOM, resolvendo problemas de estilo e mantendo a manutenibilidade do código."