Vue.js: Sintaxe de Template
Última atualização: 2026-08-26
A sintaxe de modelos do Vue é uma extensão declarativa do HTML — ela adiciona “diretivas” e “interpolação” ao HTML padrão, permitindo que você renderize dados no DOM. A sintaxe de modelos como um todo é 90% semelhante ao HTML, portanto, qualquer pessoa familiarizada com HTML pode começar a usá-la em 30 minutos.
No cerne dos modelos do Vue estão três elementos-chave: interpolação (exibição de dados) + diretivas (vinculação de propriedades/eventos) + expressões (execução de JavaScript). Esta lição irá guiá-lo pelas formas padrão de utilizar esses três elementos.
1. O que você vai aprender
- 4 maneiras de usar a interpolação de bigode
{{ }} - As diferenças entre as três diretivas de texto: v-text, v-html e v-once
- v-bind: Abreviação de “ligação de atributos”
:e sintaxe de objetos - v-on: Abreviação para o binding de evento
@e modificadores - Restrições às expressões em JS disponíveis nos modelos (não são permitidas instruções)
- As finalidades das duas diretivas do Border, v-pre e v-cloak
2. Requisitos de exibição para a ficha de um produto no comércio eletrônico
(1) Desafio: 30 produtos — copiar e colar 30 vezes?
Alice está desenvolvendo um painel de administração para um site de comércio eletrônico. Ela precisa exibir 30 fichas de produtos. Sua abordagem inicial:
<!-- Copy and paste 30 times, nightmare... -->
<div class="product-card">
<h3>iPhone 15 Pro</h3>
<p>Price: $999</p>
<button>Edit</button>
</div>
<div class="product-card">
<h3>MacBook Pro</h3>
<p>Price: $2499</p>
<button>Edit</button>
</div>
<!-- ... And also 28 more ... -->
Quando o gerente de produto Charlie diz “Altere o formato do preço para mostrar o símbolo da moeda”:
“Alice, você não pode editar manualmente 30 arquivos. E se tivermos 5.000 produtos no mês que vem? Precisamos de um modelo dinâmico.”
(2) Solução com modelo do Vue: 1 componente, 30 pontos de dados
<template>
<div v-for="product in products" :key="product.id" class="product-card">
<h3>{{ product.name }}</h3>
<p>Price: ${{ product.price }}</p>
<button @click="editProduct(product.id)">Edit</button>
</div>
</template>
<script setup>
const products = ref([
{ id: 1, name: 'iPhone 15 Pro', price: 999 },
{ id: 2, name: 'MacBook Pro', price: 2499 },
// ... 30 items, each only 1 line 1 ...
])
function editProduct(id) {
console.log('Editing product', id)
}
</script>
5 linhas de código de modelo × 30 produtos = 0 copiar e colar. Quando os nomes ou preços dos produtos mudam, o Vue atualiza automaticamente o DOM.
(3) Receita
Após adotar os modelos do Vue:
- Volume do código: 30 × 15 linhas = 450 linhas → 1 modelo + 30 dados = 60 linhas (-87%)
- Custos de manutenção: Alteração do estilo de 1 modelo → 30 produtos são atualizados simultaneamente
- Adicionar um novo produto: Adicione 1 linha de dados → O modelo é exibido automaticamente
3. Interpolação: Mustache {{ }}
(1) 4 maneiras de implementar a interpolação
| Sintaxe | Resultado | Exemplo |
|---|---|---|
{{ var }} |
Exibir o valor da variável | <p>{{ username }}</p> → Alice |
{{ expr }} |
Exibir o resultado da expressão em JS | <p>{{ count + 1 }}</p> → 6 |
{{ fn() }} |
Valor de retorno da chamada de função | <p>{{ formatDate(date) }}</p> → 2 de julho de 2026 |
{{ obj.prop }} |
Acessar propriedades do objeto | <p>{{ user.name }}</p> → Alice |
(2) As 5 principais características da interpolação
<template>
<!-- 1. Automatically Responsive:Automatically updates when variables change -->
<p>{{ message }}</p>
<!-- 2. Support JS Expression(Cannot write statements)-->
<p>{{ count * 2 + 1 }}</p>
<p>{{ isVip ? 'VIP' : 'Regular' }}</p>
<p>{{ items.length > 0 ? items[0].name : 'Empty' }}</p>
<!-- 3. Supports ternary expressions、Arithmetic、Function Call -->
<p>{{ new Date().getFullYear() }}</p>
<!-- 4. Not supported: if/for/Variable Declaration (Use v-if/v-for instead) -->
<!-- ❌ {{ if (x) { y } }} // Wrong -->
<!-- ✅ <p v-if=\"x\">y</p> // Correct -->
<!-- 5. Support for template strings -->
<p>{{ `Hello, ${name}!` }}</p>
</template>
(3) A diferença entre interpolação e v-text
<template>
<!-- Interpolation:You can add other content here -->
<p>Welcome, {{ name }}!</p>
<!-- v-text:Replace the entire element's content -->
<p v-text="`Welcome, ${name}!`"></p>
<!-- The above is equivalent to <p>Welcome, {{ name }}!</p> -->
</template>
4. Diretivas de texto: v-text / v-html / v-once
(1) Comparação dos três comandos
| Comando | Função | Caso de uso | Segurança |
|---|---|---|---|
{{ }} Interpolação |
Interpolação de texto | Preferência padrão | ✅ Segurança |
v-text |
Definindo o textContent de um elemento |
Sintaxe alternativa para interpolação | ✅ Seguro |
v-html |
Definir o innerHTML de um elemento | Renderizar texto formatado (Use com cautela) | ⚠️ Risco de XSS |
v-once |
Renderização única, sem atualizações posteriores | Otimização estática | ✅ Segurança |
(2) Aviso do v-html
<template>
<!-- ✅ Safety:Content You Control -->
<div v-html="markdownHtml"></div>
<!-- ❌ Danger:User-entered HTML -->
<div v-html="userComment"></div>
<!-- User Input <script>alert('hacked')</script> Will be executed! -->
<!-- ✅ Recommended: Use DOMPurify Cleanup -->
<div v-html="DOMPurify.sanitize(userComment)"></div>
</template>
(3) Otimização de desempenho do v-once
<template>
<!-- This productId won't change → Use v-once to Reduce Reactive Overhead -->
<span v-once>{{ productId }}</span>
<!-- But this currentPrice Will change → Do not use v-once -->
<span>{{ currentPrice }}</span>
</template>
5. Vinculação de propriedades: v-bind e a sintaxe abreviada :
(1) Noções básicas do v-bind
<template>
<!-- Complete Syntax -->
<img v-bind:src="imageUrl" v-bind:alt="imageAlt">
<!-- Abbreviation(Recommendations):Remove v-bind,Only : -->
<img :src="imageUrl" :alt="imageAlt">
<!-- Dynamic property names(v-bind Special Syntax)-->
<img :[dynamicAttr]="value">
</template>
(2) Vinculação de classe e estilo (Ponto-chave)
<template>
<!-- 1. Object Syntax:Switch Based on Conditions class -->
<div :class="{ active: isActive, 'text-danger': hasError }">
Conditional classes
</div>
<!-- 2. Array Syntax:Apply multiple at the same time class -->
<div :class="[activeClass, errorClass]">
Multiple classes
</div>
<!-- 3. Trinomial Expression(Array + Object)-->
<div :class="[isActive ? 'active' : '', { 'has-error': hasError }]">
Mixed
</div>
<!-- 4. Nested Objects in an Array -->
<div :class="[{ active: isActive }, errorClass]">
Nested
</div>
</template>
(3) Estilo de ligação
<template>
<!-- 1. Object Syntax -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
Styled
</div>
<!-- 2. Array Syntax(Merge multiple style Object)-->
<div :style="[baseStyles, overridingStyles]">
Multi styles
</div>
<!-- 3. CSS Variable(Vue 3 Recommendations)-->
<div :style="{ '--main-color': mainColor }">
With CSS variables
</div>
</template>
6. Vinculação de eventos: v-on e a sintaxe abreviada @
(1) Noções básicas do v-on
<template>
<!-- Complete Syntax -->
<button v-on:click="handleClick">Click</button>
<!-- Abbreviation(Recommendations)-->
<button @click="handleClick">Click</button>
<!-- Inline Processor(Write the simple logic directly)-->
<button @click="count++">Increment</button>
<!-- Passing Parameters -->
<button @click="handleClick('arg1', $event)">Click</button>
</template>
(2) Modificadores comuns de eventos (9)
<template>
<!-- 1. .stop:Prevent Bubbling(e.stopPropagation())-->
<button @click.stop="handleClick">Stop propagation</button>
<!-- 2. .prevent:Prevent the default behavior(e.preventDefault())-->
<form @submit.prevent="handleSubmit">Submit</form>
<!-- 3. .once:Triggered only once -->
<button @click.once="handleClick">Click once</button>
<!-- 4. .self:Only event.target This is triggered only if it is the current element -->
<div @click.self="handleClick">Self only</div>
<!-- 5. .capture:Using the Capture Phase -->
<div @click.capture="handleClick">Capture</div>
<!-- 6. .passive:Do not prevent the default behavior(Performance Optimization)-->
<div @scroll.passive="onScroll">Passive scroll</div>
<!-- 7. Keyboard Modifiers -->
<input @keyup.enter="submit"> <!-- Enter Key -->
<input @keyup.esc="cancel"> <!-- Esc Key -->
<input @keyup.tab="nextField"> <!-- Tab Key -->
<input @keyup.delete="deleteItem"> <!-- Delete Key -->
<!-- 8. Mouse Modifiers -->
<button @click.left="leftClick">Left</button>
<button @click.right.prevent="rightClick">No right click</button>
<button @click.middle="middleClick">Middle</button>
<!-- 9. System Modifiers(Key combinations)-->
<button @click.ctrl="save">Ctrl + Click</button>
<button @click.ctrl.exact="save">Accurate Ctrl(You cannot add other keys)</button>
</template>
(3) Cadeias de modificadores
<template>
<!-- Combinations of Multiple Modifiers(Execute in order)-->
<button @click.stop.prevent="handle">Stop + Prevent</button>
<!-- is effectively equivalent to -->
<button @click="(e) => { e.stopPropagation(); e.preventDefault(); handle(); }">
Manual
</button>
</template>
7. Restrições de expressão (não são permitidas declarações)
(1) Expressões de modelo x instruções em JavaScript
<template>
<!-- ✅ Sure.(Expression,Has a return value)-->
<p>{{ count + 1 }}</p>
<p>{{ isVip ? 'VIP' : 'Regular' }}</p>
<p>{{ items.filter(i => i.active).length }}</p>
<!-- ❌ That won't work.(Statement,No return value)-->
<!-- <p>{{ const x = 1 }}</p> // Wrong: Variable Declaration -->
<!-- <p>{{ if (x) { y } }}</p> // Wrong: if Statement -->
<!-- <p>{{ for (i of arr) {} }}</p> // Wrong: for Loop -->
<!-- ✅ Switch to v-if / v-for Instructions -->
<p v-if="x">y</p>
<div v-for="i in arr" :key="i.id">{{ i.name }}</div>
</template>
(2) Expressões x Propriedades calculadas
| Cenário | Uso de expressões | Uso de propriedades calculadas |
|---|---|---|
| Cálculos simples (1–2 operadores) | ✅ | ❌ |
| Lógica complexa (várias etapas, loops) | ❌ | ✅ |
| Armazenamento em cache necessário | ❌ | ✅ |
| Parâmetros obrigatórios | ✅ | ❌ (Use um método em vez disso) |
<template>
<!-- Simple:Expression -->
<p>{{ price * quantity }}</p>
<!-- Complex:Computed Properties(computed)-->
<p>{{ totalWithTax }}</p>
</template>
8. Diretivas do Edge: v-pre / v-cloak
(1) v-pre: Ignorar a compilação
<template>
<!-- Skip compiling this element,{{ }} Display as is -->
<span v-pre>{{ this is literal, not interpolated }}</span>
</template>
Cenário: Demonstrar a sintaxe dos modelos do Vue em si (por exemplo, tutoriais, sites de documentação).
(2) v-cloak: Ocultar antes da compilação
<template>
<!-- v-cloak Make elements invisible until compilation is complete -->
<p v-cloak>{{ message }}</p>
</template>
<style>
[v-cloak] { display: none; }
</style>
Cenário: Impedir que os usuários vejam {{ message }} piscando antes da compilação.
9. Exemplo completo: Cartões dinâmicos de produtos
▶ Exemplo: 1. Ficha básica do produto
<template>
<div class="product-card">
<h3>{{ product.name }}</h3>
<p class="price">${{ product.price }}</p>
<span :class="['badge', stockStatusClass]">
{{ stockStatusText }}
</span>
<button :disabled="product.stock === 0" @click="addToCart">
Add to Cart
</button>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
product: { type: Object, required: true }
})
const stockStatusClass = computed(() => {
if (props.product.stock === 0) return 'out-of-stock'
if (props.product.stock < 10) return 'low-stock'
return 'in-stock'
})
const stockStatusText = computed(() => {
if (props.product.stock === 0) return 'Out of Stock'
if (props.product.stock < 10) return `Only ${props.product.stock} left`
return 'In Stock'
})
function addToCart() {
console.log('Added', props.product.name)
}
</script>
▶ Exemplo: 2. Várias maneiras de escrever a interpolação
<template>
<!-- Variable Interpolation -->
<p>{{ username }}</p>
<!-- Expression Interpolation -->
<p>{{ count * 2 + 1 }}</p>
<!-- Trinomial Expression -->
<p v-if="score >= 60">Passed</p>
<p v-else>Failed</p>
<!-- Function Call -->
<p>{{ formatDate(new Date()) }}</p>
<!-- Object Access -->
<p>{{ user.profile.name }}</p>
</template>
▶ Exemplo: 3. 5 maneiras de usar o v-bind
<template>
<!-- 1. Basic Binding -->
<img :src="imageUrl">
<!-- 2. Dynamic property names -->
<img :[attrName]="value">
<!-- 3. Object Syntax(class)-->
<div :class="{ active: isActive }">A</div>
<!-- 4. Array Syntax(class)-->
<div :class="[activeClass, errorClass]">B</div>
<!-- 5. String Concatenation(href)-->
<a :href="`/users/${userId}`">Profile</a>
</template>
▶ Exemplo: 4. 5 maneiras de escrever “v-on”
<template>
<!-- 1. Just a click -->
<button @click="handleClick">Click</button>
<!-- 2. Inline Statements -->
<button @click="count++">+</button>
<!-- 3. Passing Parameters + event Object -->
<button @click="handleDelete(item.id, $event)">Delete</button>
<!-- 4. Modifier Chain -->
<button @click.stop.prevent="handle">Save</button>
<!-- 5. Keyboard shortcuts -->
<input @keyup.ctrl.enter="submit">
</template>
▶ Exemplo: 5. Modelos x Referência rápida em HTML
| Elemento | Sintaxe HTML | Sintaxe Vue | Descrição |
|---|---|---|---|
| Texto estático | <p>Hello</p> |
<p>{{ msg }}</p> |
Tornar dinâmico |
| Propriedade estática | <img src="x.jpg"> |
<img :src="url"> |
Alterar para dinâmica |
| Classe estática | <div class="box"> |
<div :class="cls"> |
Alterar para dinâmica |
| Estilo estático | <div style="color: red"> |
<div :style="sty"> |
Alterar para dinâmico |
| Evento de clique | <button onclick="fn()"> |
<button @click="fn"> |
Editar Vue |
| Renderização condicional | Nenhuma | <div v-if="ok"> |
Específico do Vue |
| Renderização de lista | Nenhuma | <div v-for="i in list"> |
Específico do Vue |
▶ Exemplo: 6. 5 erros comuns
| Erro | Sintoma | Solução |
|---|---|---|
Esqueça .value |
Escreva {{ count }} no modelo e count.value no JS |
O modelo o descompacta automaticamente; o JS precisa de .value |
Como escrever if/for em {{ }} |
Erros de compilação | Como usar as diretivas v-if / v-for |
| v-html XSS | Entrada do usuário <script> Execução |
Limpar com DOMPurify |
| A função inline não recebeu $event | Não é possível acessar o objeto de evento | @click="fn($event)" Passe explicitamente |
| Ordem incorreta dos modificadores | Não foi possível impedir a propagação | .stop precede .prevent (conforme necessário) |
❓ Perguntas Frequentes
P: Qual é a diferença entre
{{ }}e v-text? R: Ambos têm o mesmo efeito — definem otextContentdo elemento. A diferença é que{{ }}pode ser usado junto com texto estático (<p>Hi, {{ name }}</p>), enquanto v-text deve ocupar todo o elemento (<p v-text="msg"></p>). Recomendamos usar{{ }}para maior flexibilidade.
P: O v-html é seguro? R: Não, não é seguro. O v-html analisa strings como HTML, permitindo que usuários mal-intencionados injetem
<script>para atacar seu site. A menos que você confie 100% na fonte dos dados (como um renderizador de Markdown que você mesmo escreveu), use{{ }}para interpolação ou limpe os dados primeiro comDOMPurify.sanitize().
P: Qual é a ordem de execução da cadeia de modificadores v-on? R: Da esquerda para a direita. Por exemplo,
@click.stop.preventchamastopPropagation()primeiro, seguido porpreventDefault().
P: É possível abreviar
v-bind? Também é possível abreviarv-on? R: A abreviação parav-bindé:(semv-bind), e a abreviação parav-oné@(recomendado). Ambas as abreviações são notações oficiais suportadas desde o Vue 2 e são compatíveis com o realce de sintaxe e o autocompletamento do IDE.
P: Posso usar instruções if/else em modelos? R: Não. As expressões de modelo devem retornar um valor, e if/for são instruções, não expressões. Use as diretivas v-if, v-else-if e v-else para implementar a renderização condicional.
P: Como alternar dinamicamente entre várias classes usando o v-bind? R: Use a sintaxe de objeto
:class="{ active: isActive, 'text-danger': hasError }". Ou a sintaxe de array:class="[activeClass, errorClass]". É possível misturar as duas::class="[isActive && 'active', { 'error': hasError }]".
P: Como faço para detectar o atalho de teclado Ctrl+S? R: Use o modificador de sistema
@keyup.ctrl.s="save"ou@keydown.ctrl.s.prevent="save"(adicione .prevent para impedir que a caixa de diálogo padrão de salvamento do navegador seja exibida). O Vue 3 também suporta o modificador exato.exact; por exemplo,@click.ctrl.exactexige que apenas a tecla Ctrl seja pressionada — nenhuma outra tecla é permitida.
📖 Resumo
- A sintaxe dos modelos do Vue é uma extensão declarativa do HTML, centrada na interpolação, nas diretivas e nas expressões.
- A interpolação Mustache
{{ }}suporta variáveis, expressões, chamadas de função e operadores ternários - As expressões de modelo não podem conter instruções if/for nem declarações de variáveis (use as diretivas v-if e v-for em vez disso)
- v-text substitui todo o texto do elemento; v-html renderiza texto formatado (risco de XSS)
- otimização de desempenho com v-once (o conteúdo estático é renderizado apenas uma vez), v-pre ignora a compilação
- v-bind (abreviado como
:), v-on (abreviado como@) e três métodos comuns de ligação de classes - 9 modificadores de evento: .stop / .prevent / .once / .self / .capture / .passive + teclado/mouse/sistema
- Escolha entre expressões de modelo e propriedades calculadas: use expressões para casos simples e propriedades calculadas para casos complexos
📝 Exercícios
-
Questões básicas (Dificuldade: ⭐)
Escreva um componente Vue 3 que exiba um cartão de usuário contendo:
- Nome de usuário (interpolação)
- Foto de perfil (v-bind)
- Botão “Seguir” (v-on)
- Status online/offline (alternância dinâmica)
Dados utilizados: Objeto de usuário
{ name: 'Alice', avatar: 'alice.jpg', online: true } -
Problemas avançados (Dificuldade: ⭐⭐)
Crie uma lista de tarefas simples:
- Campo de entrada (o v-model não está sendo usado no momento; simulado usando v-bind:value + @input)
- Botão “Adicionar” (@click + expressão)
- Renderização de listas (v-for; abordaremos isso na próxima aula)
- Cada item possui um botão para excluir (@clique + passar parâmetros)
Dados:
const todos = ref(['Learn Vue', 'Build app', 'Deploy']) -
Problema desafiador (Dificuldade: ⭐⭐⭐)
Escreva um componente de ficha técnica completa do produto que ofereça suporte a:
- 5 ícones de status do produto (Em estoque/Esgotado/Pré-venda/Em promoção/Descontinuado)
- Os 5 estados correspondem a cores de classe diferentes (definidas em bloco usando a sintaxe de objeto)
- Botões para aumentar/diminuir a quantidade (@clique para aumentar/diminuir)
- Desativar o botão “Adicionar ao carrinho” quando a quantidade for maior ou igual ao estoque (:desativado dinamicamente)
- Pelo menos 3 modificadores de evento (@click.stop, @submit.prevent, etc.)