Vue.js: Renderização Condicional e Listas
Última atualização: 2026-08-26
A renderização condicional (v-if / v-show) e a renderização de lista (v-for) são as duas diretivas mais utilizadas nos modelos do Vue. A primeira controla se um elemento é exibido ou não, enquanto a segunda controla quantos elementos são exibidos. Juntas, elas são capazes de lidar com 90% dos cenários de interface do usuário.
Compreender as cinco principais diferenças entre v-if e v-for, bem como o uso correto de :key, é essencial para a criação de aplicativos Vue de alto desempenho.
1. O que você vai aprender
Renderização condicional
- As 5 principais diferenças entre
v-showev-if v-forIteração sobre matrizes, objetos, números e strings- O objetivo e as melhores práticas do
:key templateEnvolvendo vários elementos- Filtragem, ordenação e paginação de listas
- Otimização da rolagem virtual (mais de 10.000 itens)
2. Um pesadelo de programação: “Instruções if aninhadas” em um painel
(1) Problema: 5 tags v-if aninhadas — um pesadelo para a depuração
O código do painel da Alice ficou fora de controle:
<!-- ❌ The "Broken" Version:v-if Nested Hell -->
<template>
<div v-if="user">
<div v-if="user.isAdmin">
<div v-if="orders.length > 0">
<div v-if="!isLoading">
<div v-if="!hasError">
<!-- Content is finally displayed -->
<p>Orders: {{ orders.length }}</p>
</div>
<div v-else>Error: {{ errorMsg }}</div>
</div>
<div v-else>Loading...</div>
</div>
<div v-else>No orders</div>
</div>
<div v-else>Not admin</div>
</div>
<div v-else>Please login</div>
</template>
A revisão de código feita por Charlie:
“Alice, isso está impossível de ler. São cinco níveis de aninhamento de v-if. E se adicionarmos mais duas condições? Precisamos de um padrão mais claro.”
(2) Solução com Vue: Simplificação com v-if + Propriedades calculadas
<template>
<div v-if="!user">Please login</div>
<div v-else-if="!user.isAdmin">Not admin</div>
<div v-else-if="isLoading">Loading...</div>
<div v-else-if="hasError">Error: {{ errorMsg }}</div>
<div v-else-if="orders.length === 0">No orders</div>
<div v-else>
<p>Orders: {{ orders.length }}</p>
<OrderList :orders="orders" />
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['user', 'orders', 'isLoading', 'hasError', 'errorMsg'])
// Flatten 5 conditions (v-else-if chain)
</script>
(3) Receita
- Legibilidade: 5 níveis de aninhamento → 6 linhas dispostas lado a lado
- Fácil de manter: Para adicionar uma condição, basta inserir uma linha de
v-else-if - Depuração: Cada linha tem sua própria condição, o que facilita a definição de pontos de interrupção
3. Renderização condicional com v-if / v-else-if / v-else
(1) Sintaxe básica
<template>
<!-- Single condition -->
<p v-if="isVisible">Visible</p>
<!-- Choose one of the two -->
<p v-if="score >= 60">Passed</p>
<p v-else>Failed</p>
<!-- Choose one or more -->
<p v-if="type === 'A'">Type A</p>
<p v-else-if="type === 'B'">Type B</p>
<p v-else-if="type === 'C'">Type C</p>
<p v-else>Unknown</p>
</template>
(2) O elemento template
<template>
<!-- template:Grouping Multiple Elements,Do not render DOM -->
<template v-if="isAdmin">
<h1>Admin Dashboard</h1>
<p>Welcome, {{ user.name }}</p>
<button>Edit</button>
</template>
<!-- Does not render template element (Savings 1 DOM layer) -->
</template>
(3) 5 principais casos de uso
| Cenário | Usando v-if |
|---|---|
| Conectado/Não conectado | ✅ |
| Carregando.../Carregado | ✅ |
| Sem dados/Dados disponíveis | ✅ |
| Status do erro | ✅ |
| Alternar frequentemente entre mostrar e ocultar | ❌ (Use v-show) |
4. 5 principais diferenças entre v-show e v-if
(1) Principais diferenças
<template>
<!-- v-if: When conditions are false, Elements removed from DOM -->
<p v-if="show">v-if</p>
<!-- show=false: The element does not exist in DOM -->
<!-- v-show: When conditions are false, display: none -->
<p v-show="show">v-show</p>
<!-- show=false: <p style="display: none">v-show</p> -->
</template>
(2) 5 comparações importantes
| Aspecto | v-if | v-show |
|---|---|---|
| Manipulação do DOM | Remove o elemento quando a condição é falsa | Permanece sempre no DOM; apenas a propriedade display é alterada |
| Desempenho da troca | Ruim (destruir e reconstruir a cada vez) | Bom (apenas CSS) |
| Renderização inicial | Não renderizar se a condição for falsa | Sempre renderizar (mesmo que seja invisível) |
| Compatibilidade com transições | ✅ Perfeita (animações de entrada/saída) | ❌ Não compatível |
| Casos de uso | Troca ocasional | Troca frequente |
(3) Recomendações de seleção
// Switch Occasionally(If you are logged in/Not logged in)→ v-if
<div v-if="isLoggedIn">Welcome, {{ user.name }}</div>
<div v-else>Please login</div>
// Frequent switching (e.g. tab switch) → v-show
<button v-show="isActive">Active</button>
<button v-show="!isActive">Inactive</button>
5. Exibição de listas com v-for
(1) Iterando por um array
<template>
<!-- Basic Iteration -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- Indexed -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' }
])
</script>
(2) Iteração sobre objetos
<template>
<ul>
<li v-for="(value, key, index) in user" :key="key">
{{ index + 1 }}. {{ key }}: {{ value }}
</li>
</ul>
</template>
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: 'Alice',
age: 25,
email: 'alice@example.com'
})
// Output:1. name: Alice 2. age: 25 3. email: alice@example.com
</script>
(3) Iterando por números
<template>
<!-- Rendering 1 to 10 -->
<span v-for="n in 10" :key="n">{{ n }}</span>
<!-- Output:12345678910 -->
</template>
(4) Iteração por uma string
<template>
<!-- One character per line span -->
<span v-for="char in 'Hello'" :key="char">{{ char }}</span>
<!-- Output:H e l l o -->
</template>
6. A finalidade de :key e as melhores práticas
(1) O que é :key?
:key Atribua um identificador exclusivo a cada item do v-for para ajudar o Vue a atualizar o DOM de forma eficiente.
<template>
<!-- ❌ None :key(Vue Use by default index) -->
<li v-for="item in items">{{ item.name }}</li>
<!-- ✅ Has :key (Use "unique" id) -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</template>
(2) Por que o :key é necessário?
graph LR A[Original List A B C] --> B[New List B C D] B --> C[None key:Possible misalignment] B --> D[Has key: Update Correctly] style C fill:#ff6b6b style D fill:#42b883
Quando não há :key:
- Comparando o uso de
indexno Vue - Depois de remover A, B/C passa a ser (0)/(1), mas o Vue considera que “B/C ainda está lá”
- Provoca um desalinhamento no estado dos componentes
Quando :key estiver presente:
- Comparando IDs no Vue
- Identificar com precisão quais itens foram adicionados, excluídos ou movidos
- Desempenho ideal
(3) Melhores práticas para :key
<!-- ✅ Recommendations:The Only One ID -->
<li v-for="user in users" :key="user.id">
<!-- ✅ Second choice:Unique Field(Email、Mobile phone number, etc.)-->
<li v-for="user in users" :key="user.email">
<!-- ❌ Avoid:index(When the data changes, the alignment is off.)-->
<li v-for="(user, index) in users" :key="index">
<!-- ❌ Never, under any circumstances:random()(Every render is different,Forced Rebuild)-->
<li v-for="user in users" :key="Math.random()">
7. Filtragem / Ordenação / Paginação de listas
(1) Filtragem + Classificação (Propriedades calculadas)
import { ref, computed } from 'vue'
const items = ref([
{ id: 1, name: 'iPhone', price: 999, category: 'phone' },
{ id: 2, name: 'MacBook', price: 2499, category: 'laptop' },
{ id: 3, name: 'iPad', price: 599, category: 'tablet' }
])
const searchQuery = ref('')
const selectedCategory = ref('all')
const sortBy = ref('price') // 'price' / 'name'
// Filter + Sort(computed Automatic Caching)
const filteredItems = computed(() => {
let result = items.value
// 1. Filter by search term
if (searchQuery.value) {
result = result.filter(item =>
item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
}
// 2. Filter by Category
if (selectedCategory.value !== 'all') {
result = result.filter(item => item.category === selectedCategory.value)
}
// 3. Sort
result = [...result].sort((a, b) => {
if (sortBy.value === 'price') return a.price - b.price
if (sortBy.value === 'name') return a.name.localeCompare(b.name)
return 0
})
return result
})
(2) Paginação
const currentPage = ref(1)
const pageSize = ref(10)
// Data after pagination
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return filteredItems.value.slice(start, start + pageSize.value)
})
// Total Number of Pages
const totalPages = computed(() =>
Math.ceil(filteredItems.value.length / pageSize.value)
)
8. Exemplo completo: Lista dinâmica de produtos
▶ Exemplo: 1. 5 maneiras de usar v-if/v-show
<template>
<!-- 1. v-if:Conditional Rendering(Not here DOM) -->
<p v-if="show">v-if</p>
<!-- 2. v-show:display none(Always there DOM) -->
<p v-show="show">v-show</p>
<!-- 3. v-else-if:Chained Conditions -->
<p v-if="type === 'A'">A</p>
<p v-else-if="type === 'B'">B</p>
<p v-else>C</p>
<!-- 4. v-if + template:Multi-element -->
<template v-if="isAdmin">
<h1>Admin</h1>
<button>Edit</button>
</template>
<!-- 5. v-if vs v-show Performance Comparison -->
<div v-if="occasionally">Switch Occasionally</div>
<div v-show="frequently">Frequent switching(Not recommended for use in combination with `<transition>`,v-if That's the only way)</div>
</template>
▶ Exemplo: 2. 5 maneiras de usar v-for
<template>
<!-- 1. Array -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- 2. Array + Index -->
<li v-for="(item, i) in items" :key="item.id">
{{ i + 1 }}. {{ item.name }}
</li>
<!-- 3. Object -->
<li v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</li>
<!-- 4. Numbers -->
<span v-for="n in 5" :key="n">{{ n }}</span>
<!-- 5. String -->
<span v-for="char in 'Hello'" :key="char">{{ char }}</span>
</template>
▶ Exemplo: 3. Comparação de 5 maneiras de usar :key
<template>
<!-- 1. The Only One ID(Best) -->
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
<!-- 2. Unique Field -->
<li v-for="user in users" :key="user.email">{{ user.name }}</li>
<!-- 3. Composite key -->
<li v-for="post in posts" :key="`${post.userId}-${post.id}`">
{{ post.title }}
</li>
<!-- 4. Use index (Second choice) -->
<li v-for="(item, i) in items" :key="i">{{ item.name }}</li>
<!-- 5. ❌ random()(Never, under any circumstances) -->
<li v-for="item in items" :key="Math.random()">{{ item.name }}</li>
</template>
▶ Exemplo: 4. Exemplo completo de filtragem + ordenação + paginação de listas
<template>
<div>
<!-- Search box -->
<input v-model="searchQuery" placeholder="Search...">
<!-- Filter by Category -->
<select v-model="selectedCategory">
<option value="all">All</option>
<option value="phone">Phone</option>
<option value="laptop">Laptop</option>
</select>
<!-- Sort -->
<select v-model="sortBy">
<option value="price">Price</option>
<option value="name">Name</option>
</select>
<!-- List -->
<ul>
<li v-for="item in paginatedItems" :key="item.id">
{{ item.name }} - ${{ item.price }}
</li>
</ul>
<!-- Pagination -->
<button :disabled="currentPage === 1" @click="currentPage--">
Previous
</button>
<span>{{ currentPage }} / {{ totalPages }}</span>
<button :disabled="currentPage === totalPages" @click="currentPage++">
Next
</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ id: 1, name: 'iPhone', price: 999, category: 'phone' },
{ id: 2, name: 'MacBook', price: 2499, category: 'laptop' },
{ id: 3, name: 'iPad', price: 599, category: 'tablet' }
])
const searchQuery = ref('')
const selectedCategory = ref('all')
const sortBy = ref('price')
const currentPage = ref(1)
const pageSize = 10
const filteredItems = computed(() => {
let result = items.value
if (searchQuery.value) {
result = result.filter(i => i.name.includes(searchQuery.value))
}
if (selectedCategory.value !== 'all') {
result = result.filter(i => i.category === selectedCategory.value)
}
return [...result].sort((a, b) => {
if (sortBy.value === 'price') return a.price - b.price
return a.name.localeCompare(b.name)
})
})
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * pageSize
return filteredItems.value.slice(start, start + pageSize)
})
const totalPages = computed(() =>
Math.ceil(filteredItems.value.length / pageSize)
)
</script>
▶ Exemplo: 5. 5 erros comuns
| Erro | Sintoma | Solução |
|---|---|---|
| Nenhuma :key | Desalinhamento da lista, baixo desempenho | Adicionar :key="item.id" |
| Como usar v-for e v-if juntos | Confusão sobre a prioridade do v-if | Envolvendo com template ou usando computed |
| Use “índice” como chave | Desalinhamento durante a classificação | Use um ID exclusivo |
O v-show não é compatível com <transition> |
Não é possível criar animações | Use v-if em vez disso + <transition> |
| A lista é muito grande (mais de 100.000) | A página trava | Use o vue-virtual-scroller para rolagem virtual |
▶ Exemplo: 6. Prioridade entre v-if e v-for
<!-- In Vue 3, v-if has higher priority than v-for (Vue 2 is the opposite) -->
<ul>
<!-- Vue 3: First determine v-if, then v-for -->
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
<!-- ✅ But Vue officially recommends using template to separate -->
<!-- ❌ Incorrect wording(Not recommended) -->
<li v-for="user in users" v-if="shouldShowUsers(user)" :key="user.id">
{{ user.name }}
</li>
<!-- ✅ Recommendations:computed Filter -->
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</template>
❓ Perguntas Frequentes
P: Como faço para escolher entre v-if e v-show? R: Use v-if para alternâncias ocasionais (para economizar na renderização inicial) e v-show para alternâncias frequentes (para evitar destruir e reconstruir o DOM repetidamente). Use v-if para estados de login/logout e v-show para alternância entre abas.
P: O
:keyé obrigatório quando se usa ov-for? R: Recomendamos enfaticamente o uso do:keypara evitar problemas de desempenho e incompatibilidades no estado dos componentes. O ideal é usar um ID exclusivo (item.id), seguido peloindex; nunca use orandom().
P: Qual é a prioridade ao usar v-if e v-for juntos? R: No Vue 3, o v-if tem precedência sobre o v-for (ao contrário do Vue 2). No entanto, a recomendação oficial é evitar usá-los juntos; em vez disso, separe-os usando o
templateou filtre usando ocomputed.
P: Qual é a finalidade do elemento
template? R: Otemplateé um elemento wrapper lógico do Vue que não é renderizado no DOM. Ele é usado para evitar elementosdivdesnecessários quandov-ifouv-forincluem vários elementos.
P: O que posso fazer se o desempenho da lista for ruim? R: Três otimizações: (1) Adicione
:key; (2) Usecomputedpara armazenar em cache os resultados da filtragem/classificação; (3) Para listas grandes (mais de 100.000), usevue-virtual-scrollerpara rolagem virtual, a fim de renderizar apenas a área visível.
P: É possível acessar chaves e valores ao iterar sobre um objeto com
v-for? R: Sim.v-for="(value, key, index) in obj", ordem dos parâmetros: valor, chave, índice.
P: Como faço para exibir uma mensagem de aviso quando não há dados? R: Use uma combinação de v-if e v-else:
<div v-if="items.length">Column </div><div v-else> Data</div>.
P: Como faço para destacar um item selecionado em uma lista? R: Adicione um
activeIndexa cada item:@click="activeIndex = item.id" :class="{ active: activeIndex === item.id }".
📖 Resumo
- A renderização condicional com v-if (remove do DOM), enquanto v-show alterna a propriedade
display(que permanece sempre no DOM) - Use
v-ifpara trocas ocasionais ev-showpara trocas frequentes - Use
v-forpara percorrer matrizes, objetos, números e strings; use:keypara melhorar o desempenho - :key: O ideal é usar um ID exclusivo; um índice é a segunda melhor opção; não use random()
- O elemento
templateengloba vários elementos e não é renderizado no DOM - Armazenar em cache automaticamente a filtragem, a ordenação e a paginação de listas usando
computed - No Vue 3,
v-iftem precedência sobrev-for - Otimização de listas extensas com rolagem virtual (mais de 100.000 itens)
📝 Exercícios
-
Questões básicas (Dificuldade: ⭐)
Use v-if/v-else para alternar entre os estados de conectado e desconectado:
- Conectado: exibe o nome de usuário e o botão “Sair”
- Não conectado: exibe a mensagem “Faça login” e o formulário de login
- Use um template para agrupar vários elementos
-
Problemas avançados (Dificuldade: ⭐⭐)
Como criar uma lista de tarefas usando
v-for:- 3 status: Todos / Em andamento / Concluídos (alterne entre eles usando os 3 botões)
- A lista exibe as tarefas com o status correspondente
- Cada item possui os botões “Concluir” e “Excluir”
- A mensagem “Inacabado: N itens” é exibida na parte inferior
-
Problema de desafio (Dificuldade: ⭐⭐⭐)
Implementar um sistema completo de filtragem, ordenação e paginação de produtos (5 arquivos):
- Dados do produto: 30 registros, incluindo nome, preço, categoria e data de criação
- Pesquisa: Faça uma pesquisa aproximada por nome
- Filtrar por categoria: 5 categorias (todas/celular/laptop/tablet/fones de ouvido)
- Ordenar por: Preço (crescente/decrescente), Nome, Mais recentes
- Paginação: 10 itens por página, com navegação numérica entre as páginas
- Desempenho: armazena em cache os resultados calculados, filtrados e classificados
- Otimização de listas extensas: 1.000 itens de teste para garantir um desempenho sem interrupções