Capítulo 03: Sintaxe de Template, Diretivas (v-bind, v-on, v-model, v-if, v-for)
Capítulo 03: Sintaxe de Template, Diretivas (v-bind, v-on, v-model, v-if, v-for)
Especialização em Front-End com Vue • Vue 3 & Nuxt 3 • Composition API, Pinia, Vue Router, SSR e TypeScript
🗺️ Mapa Conceitual de Diretivas e Compilação de Template
flowchart TD
A["HTML Declarativo no <template>"] --> B["Vue Template Compiler (@vue/compiler-core)"]
B --> C["Diretivas de Vinculação (v-bind / :)"]
B --> D["Diretivas de Eventos (v-on / @)"]
B --> E["Diretivas de Estrutura (v-if / v-for)"]
B --> F["Two-Way Binding (v-model)"]
C --> G["AST Otimizada com Patch Flags"]
D --> G
E --> G
F --> G
G --> H["Função render() Nativa em JavaScript"]
H --> I["Reconciliação Cirúrgica sem Reflow Desnecessário"]
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style G fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style I fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. Compilação de Templates e o Poder das Diretivas
No Vue 3, os templates não são interpretados como strings no navegador; eles são compilados estaticamente durante a etapa de build (via Vite) em funções render() altamente eficientes. As diretivas são atributos especiais prefixados com v- que aplicam transformações reativas ao DOM subjacente.
1.1 v-bind (:) e Vinculação Dinâmica de Atributos
A diretiva v-bind (atalho :) liga atributos HTML ou propriedades de componentes a expressões JavaScript reativas:
- Binding de Classes e Estilos: Suporta sintaxe de objeto (
:class="{ 'bg-red-500': hasError, 'opacity-50': isDisabled }") e array (:class="[baseClass, variantClass]"). - Binding Booleano: Atributos como
disabled,checkedereadonlysão omitidos do nó do DOM se o valor fornull,undefinedoufalse.
1.2 v-on (@) e Modificadores de Evento
A diretiva v-on (atalho @) registra ouvintes de eventos nativos do DOM ou eventos customizados de componentes. Os modificadores encadeados eliminam código imperativo repetitivo:
.prevent: Executaevent.preventDefault()..stop: Invocaevent.stopPropagation(), impedindo o borbulhamento (bubbling)..passive: Otimiza a performance de scroll em dispositivos touch ao informar ao navegador que o handler não cancelará o scroll.- Modificadores de teclado:
@keyup.enter,@keydown.esc,@keydown.tab.
1.3 v-if vs v-show: Critérios de Arquitetura e Performance
v-if/v-else-if/v-else: Renderização condicional verdadeira. Os nós do DOM e seus componentes filhos são fisicamente destruídos e recriados. Ideal para seções pesadas que raramente alternam de visibilidade (ex: formulários complexos abertos sob demanda).v-show: O nó permanece sempre presente no DOM, alternando unicamente a propriedade CSSdisplay: none. Possui custo inicial de compilação maior, mas custo de alternância (toggle cost) próximo a zero. Ideal para modais frequentes, abas e tooltips.
1.4 v-for e a Regra Sagrada da Chave Única (:key)
Ao renderizar coleções, o Vue utiliza um algoritmo de reconciliação para comparar a árvore antiga com a nova. Nunca utilize o índice do array como :key se a lista sofrer ordenação, filtragem, inserção ou remoção intermediária. O uso de IDs estáveis (ex: product.id) garante que o estado interno de inputs e transições não seja associado a elementos incorretos.
⚖️ 2. Tabela Comparativa: v-if vs v-show
| Dimensão Técnica | v-if / v-else |
v-show |
|---|---|---|
| Presença no DOM | O nó é completamente removido ou montado. | O nó sempre existe; alterna display: none. |
| Custo de Inicialização | Baixo (se a condição for falsa, nada é montado). | Médio (o nó e seus filhos são instanciados no início). |
| Custo de Alternância (Toggle) | Alto (dispara ciclo de vida onMounted / onUnmounted). |
Praticamente Zero (apenas alteração de regra CSS). |
Compatibilidade com <template> |
Suportada diretamente (<template v-if="...">). |
Não suportada (a tag <template> não aceita CSS). |
Suporte a v-else |
Suporte nativo completo encadeado. | Não possui diretiva v-show-else equivalente. |
⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)
Anti-Pattern 1: Usar v-if e v-for no Mesmo Elemento
Erro Comum: Escrever <li v-for="user in users" v-if="user.isActive" :key="user.id">. No Vue 3, o v-if possui precedência de avaliação superior ao v-for. Isso resulta em erro de compilação, pois a variável de escopo user ainda não existe no momento em que a condição é avaliada.
Abordagem Correta: Pré-filtre os dados utilizando uma propriedade computada (const activeUsers = computed(() => users.value.filter(u => u.isActive))) e itere sobre a lista limpa.
Anti-Pattern 2: Usar Índice como :key em Listas Editáveis
Erro Comum: <tr v-for="(item, index) in list" :key="index">. Ao excluir o primeiro item, os inputs de texto associados mantêm os valores visualmente no primeiro índice físico, corrompendo a fidelidade dos dados visuais.
Abordagem Correta: Use sempre uma chave imutável: :key="item.uuid".
💻 4. Código Conceitual Arquitetural Comentado
<!-- src/components/DynamicFilterList.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue';
interface DatabaseInstance {
id: string;
name: string;
engine: 'postgres' | 'mysql' | 'redis';
isReplica: boolean;
}
const instances = ref<DatabaseInstance[]>([
{ id: 'db-01', name: 'Primary DB Prod', engine: 'postgres', isReplica: false },
{ id: 'db-02', name: 'Read Replica Alpha', engine: 'postgres', isReplica: true },
{ id: 'db-03', name: 'Session Cache Pod', engine: 'redis', isReplica: false }
]);
const filterText = ref<string>('');
const showOnlyReplicas = ref<boolean>(false);
// Filtragem computada pura (resolve o anti-pattern do v-if com v-for)
const filteredInstances = computed<DatabaseInstance[]>(() => {
return instances.value.filter((inst) => {
const matchesSearch = inst.name.toLowerCase().includes(filterText.value.toLowerCase().trim());
const matchesReplica = !showOnlyReplicas.value || inst.isReplica;
return matchesSearch && matchesReplica;
});
});
</script>
<template>
<div class="max-w-md mx-auto p-5 bg-white border rounded-2xl shadow-sm space-y-4">
<div class="space-y-2">
<!-- v-model com modificador trim -->
<input
type="text"
v-model.trim="filterText"
placeholder="Buscar banco de dados..."
class="w-full border px-3 py-2 rounded-lg text-sm outline-none focus:ring-2 focus:ring-indigo-500"
/>
<label class="flex items-center gap-2 text-xs text-slate-600 cursor-pointer">
<input type="checkbox" v-model="showOnlyReplicas" class="rounded text-indigo-600" />
Mostrar apenas réplicas de leitura
</label>
</div>
<!-- Lista condicional limpa com chave única estável -->
<ul v-if="filteredInstances.length > 0" class="divide-y border rounded-xl overflow-hidden">
<li
v-for="db in filteredInstances"
:key="db.id"
class="p-3 flex justify-between items-center text-sm"
>
<div>
<span class="font-medium text-slate-800">{{ db.name }}</span>
<p class="text-xs text-slate-400 uppercase font-mono">{{ db.engine }}</p>
</div>
<span
class="text-xs font-semibold px-2 py-0.5 rounded-full"
:class="db.isReplica ? 'bg-amber-100 text-amber-800' : 'bg-emerald-100 text-emerald-800'"
>
{{ db.isReplica ? 'Réplica' : 'Primário' }}
</span>
</li>
</ul>
<!-- Empty state ativado se filteredInstances estiver vazia -->
<div v-else class="p-6 text-center text-xs text-slate-400 bg-slate-50 border border-dashed rounded-xl">
Nenhuma instância corresponde aos filtros informados.
</div>
</div>
</template>
🛠️ 5. Cheat-Sheet de Diretivas e Modificadores
| Diretiva | Atalho | Modificadores Frequentes | Exemplo de Aplicação |
|---|---|---|---|
v-bind |
: |
.prop, .camel |
:class="activeClass", :disabled="isPending" |
v-on |
@ |
.prevent, .stop, .once, .enter |
@submit.prevent="save", @keyup.esc="closeModal" |
v-model |
- | .trim, .number, .lazy |
v-model.trim="email", v-model.number="price" |
v-if / v-else |
- | - | <div v-if="auth">...</div><div v-else>...</div> |
v-show |
- | - | <div v-show="isDropdownOpen">...</div> |
v-for |
- | - | <li v-for="item in items" :key="item.id"> |