Skip to content

Latest commit

 

History

History
775 lines (611 loc) · 15.6 KB

File metadata and controls

775 lines (611 loc) · 15.6 KB

Aula 02 - Templates Vue.js

Duração: 3 horas

  • Conteúdo teórico e prático: 1h30
  • Exercícios práticos: 1h30

Objetivos da Aula

Ao final desta aula, o aluno será capaz de:

  • Compreender a sintaxe de templates Vue.js
  • Usar interpolações de dados
  • Dominar as principais diretivas Vue
  • Trabalhar com eventos e manipulação de dados
  • Aplicar estilos e classes dinamicamente

1. Introdução aos Templates Vue

Templates Vue permitem criar interfaces dinâmicas e reativas. Utilizam sintaxe HTML estendida com:

  • Interpolações: Inserir dados no HTML
  • Diretivas: Instruções que modificam elementos
  • Expressões: JavaScript diretamente no template
  • Data Binding: Sincronização automática entre dados e view

2. Interpolações

2.1 Interpolação de Texto

A forma mais básica de inserir dados é usar {{ variavel }}:

<script setup>
import { ref } from 'vue'

const titulo = ref('Bem-vindo ao Vue')
const descricao = ref('Templates com Vue são muito poderosos!')
</script>

<template>
  <div>
    <h1>{{ titulo }}</h1>
    <p>{{ descricao }}</p>
    <p>A data de hoje é {{ new Date().toLocaleDateString('pt-BR') }}</p>
  </div>
</template>

2.2 Expressões JavaScript

Dentro das chaves é possível executar expressões JavaScript:

<script setup>
  import { ref } from 'vue'

  const ativo = ref(true)
  const nome = ref('joão')
  const numeros = ref([1, 3, 5, 7, 9])
</script>

<template>
  <div>
    <!-- Operações matemáticas -->
    <p>2 + 2 = {{ 2 + 2 }}</p>
    
    <!-- Operadores lógicos -->
    <p>{{ ativo ? 'Ativo' : 'Inativo' }}</p>
    
    <!-- Métodos string -->
    <p>{{ nome.toUpperCase() }}</p>
    
    <!-- Operações com arrays -->
    <p>{{ numeros.filter(n => n > 5) }}</p>
  </div>
</template>

2.3 HTML Raw (v-html)

Para renderizar HTML como código (não apenas texto):

<script setup>
  import { ref } from 'vue'

  const html = ref('<strong>Isso é negrito</strong>')
</script>

<template>
  <div>
    <!-- Interpolação normal (segura, renderiza texto) -->
    <p>{{ html }}</p>
    
    <!-- v-html (renderiza como HTML) -->
    <p v-html="html"></p>
  </div>
</template>

Atenção: Use v-html apenas com conteúdo confiável. Conteúdo de usuários pode conter ataques XSS.

3. Diretivas Principais

3.1 v-if, v-else-if, v-else

Renderização condicional:

<script setup>
  import { ref } from 'vue'

  const logado = ref(false)
  const idade = ref(25)
  const mostrarDetalhes = ref(true)
</script>

<template>
  <div>
    <!-- Mostrar se for true -->
    <p v-if="logado">Bem-vindo de volta!</p>
    <p v-else>Por favor, faça login</p>
    
    <!-- Com múltiplas condições -->
    <div v-if="idade < 18">
      <p>Você é menor de idade</p>
    </div>
    <div v-else-if="idade >= 18 && idade < 65">
      <p>Você está na idade de trabalhar, meu filho(a)</p>
    </div>
    <div v-else>
      <p>Você é aposentado</p>
    </div>
    
    <!-- Template condicional (sem elemento wrapper) -->
    <template v-if="mostrarDetalhes">
      <h3>Detalhes</h3>
      <p>Informações adicionais aqui</p>
    </template>
  </div>
</template>

3.2 v-show

Similar a v-if, mas usa CSS display: none:

<script setup>
  import { ref } from 'vue'

  const visivel = ref(true)
</script>

<template>
  <div>
    <!-- v-show continua no DOM, apenas oculto -->
    <p v-show="visivel">Este texto pode estar oculto</p>
    
    <button @click="visivel = !visivel">
      Alternar visibilidade
    </button>
  </div>
</template>

v-if vs v-show:

  • v-if: Elemento removido do DOM (melhor para itens que não alternam frequentemente)
  • v-show: Apenas display:none (melhor para alternâncias frequentes)

3.3 v-for

Renderizar listas:

<script setup>
import { ref } from 'vue'

const lista = ref(['Maçã', 'Banana', 'Laranja'])

const pessoas = ref([
  { id: 1, nome: 'Ana', idade: 28 },
  { id: 2, nome: 'Bruno', idade: 32 },
  { id: 3, nome: 'Carlos', idade: 25 }
])

const itens = ref(['Item 1', 'Item 2', 'Item 3'])

const config = ref({
  tema: 'escuro',
  idioma: 'pt-BR',
  notificacoes: true
})
</script>

<template>
  <div>
    <!-- Array de strings -->
    <ul>
      <li v-for="frutas in lista" :key="frutas">
        {{ frutas }}
      </li>
    </ul>
    
    <!-- Array de objetos -->
    <div v-for="pessoa in pessoas" :key="pessoa.id" class="card">
      <h3>{{ pessoa.nome }}</h3>
      <p>Idade: {{ pessoa.idade }}</p>
    </div>
    
    <!-- Com índice -->
    <div v-for="(item, index) in itens" :key="index">
      {{ index + 1 }}. {{ item }}
    </div>
    
    <!-- Iterando objetos -->
    <div v-for="(valor, chave) in config" :key="chave">
      {{ chave }}: {{ valor }}
    </div>
  </div>
</template>

Importância da chave :key:

  • Ajuda Vue a rastrear qual item mudou
  • Importante para componentes internos (mantém estado)
  • Use um id único, não índice quando possível

3.4 v-on (ou @)

Ouvir eventos:

<script setup>
import { ref } from 'vue'

const contador = ref(0)

const saudar = (nome) => {
  alert(`Olá, ${nome}!`)
}

const processar = (evento) => {
  console.log(evento)
}

const enviarFormulario = () => {
  console.log('Formulário enviado')
}

const buscar = () => {
  console.log('Buscando...')
}

const ativo = ref(false)
</script>

<template>
  <div>
    <!-- Clique em botão -->
    <button @click="contador++">
      Cliques: {{ contador }}
    </button>
    
    <!-- Com método -->
    <button @click="saudar('João')">
      Saudar
    </button>
    
    <!-- Evento com parâmetro e objeto evento -->
    <button @click="processar($event)">
      Processar
    </button>
    
    <!-- Modificadores de evento -->
    <form @submit.prevent="enviarFormulario">
      <!-- .prevent previne comportamento padrão -->
      <input type="text" />
      <button type="submit">Enviar</button>
    </form>
    
    <!-- Eventos de teclado -->
    <input @keyup.enter="buscar" placeholder="Digite e pressione Enter" />
    
    <!-- Eventos de mouse -->
    <div @mouseover="ativo = true" @mouseleave="ativo = false">
      {{ ativo ? 'Mouse aqui!' : 'Passe o mouse' }}
    </div>
  </div>
</template>

Modificadores de Evento Comuns:

  • .prevent - preventDefault()
  • .stop - stopPropagation()
  • .self - Apenas se o evento vem do próprio elemento
  • .once - Dispara apenas uma vez
  • .capture - Usa modo capture
  • .enter, .space, .esc - Modificadores de teclado

3.5 v-bind (ou :)

Vincular atributos e propriedades:

<script setup>
import { ref, computed } from 'vue'

const imagemUrl = ref('https://via.placeholder.com/150')
const descricaoImagem = ref('Imagem de exemplo')

const estaAtivo = ref(true)
const temErro = ref(false)

const classe1 = ref('classe-um')
const classe2 = ref('classe-dois')

const corTexto = ref('blue')
const tamanho = ref(18)

const estilosComputados = computed(() => ({
  backgroundColor: estaAtivo.value ? 'green' : 'gray',
  padding: '10px',
  borderRadius: '5px'
}))

const botaoDesabilitado = ref(false)
</script>

<template>
  <div>
    <!-- Atributo simples -->
    <img :src="imagemUrl" :alt="descricaoImagem" />
    
    <!-- Classe dinâmica -->
    <div :class="{ ativo: estaAtivo, erro: temErro }">
      Elemento com classes dinâmicas
    </div>
    
    <!-- Array de classes -->
    <div :class="[classe1, classe2]">
      Classes de array
    </div>
    
    <!-- Estilo inline dinâmico -->
    <div :style="{ color: corTexto, fontSize: tamanho + 'px' }">
      Texto com estilo dinâmico
    </div>
    
    <!-- Objeto de estilos -->
    <div :style="estilosComputados">
      Estilo com objeto
    </div>
    
    <!-- Atributo booleano -->
    <button :disabled="botaoDesabilitado">
      Clique aqui
    </button>
  </div>
</template>

<style scoped>
.ativo {
  background-color: #42b883;
  color: white;
}

.erro {
  border: 2px solid red;
}

.classe-um {
  font-weight: bold;
}

.classe-dois {
  text-transform: uppercase;
}

img {
  max-width: 30%;
  height: auto;
}
</style>

3.6 v-model

Two-way data binding (sincronização bidirecional):

<script setup>
import { ref } from 'vue'

const nome = ref('')
const mensagem = ref('')
const aceitar = ref(false)
const opcao = ref('opcao1')
const pais = ref('')
const interesses = ref([])
</script>

<template>
  <div>
    <!-- Input de texto -->
    <label>Nome:</label>
    <input v-model="nome" />
    <p>Você digitou: {{ nome }}</p>
    
    <!-- Textarea -->
    <label>Mensagem:</label>
    <textarea v-model="mensagem"></textarea>
    
    <!-- Checkbox -->
    <label>
      <input type="checkbox" v-model="aceitar" />
      Aceito os termos
    </label>
    <p>{{ aceitar ? 'Aceito!' : 'Não aceito' }}</p>
    
    <!-- Radio -->
    <label>
      <input type="radio" v-model="opcao" value="opcao1" />
      Opção 1
    </label>
    <label>
      <input type="radio" v-model="opcao" value="opcao2" />
      Opção 2
    </label>
    
    <!-- Select -->
    <select v-model="pais">
      <option value="">Selecione um país</option>
      <option value="br">Brasil</option>
      <option value="pt">Portugal</option>
      <option value="us">Estados Unidos</option>
    </select>
    
    <!-- Multiple select -->
    <select v-model="interesses" multiple>
      <option value="vue">Vue</option>
      <option value="react">React</option>
      <option value="angular">Angular</option>
    </select>
  </div>
</template>

4. Exemplos Práticos

Exemplo 1: Formulário com Validação

<script setup>
import { ref, computed } from 'vue'

const email = ref('')
const senha = ref('')
const enviando = ref(false)

const emailValido = computed(() => {
  return email.value.includes('@') && email.value.includes('.')
})

const senhaFraca = computed(() => {
  return senha.value.length < 6
})

const formularioValido = computed(() => {
  return emailValido.value && !senhaFraca.value
})

const enviar = async () => {
  enviando.value = true
  // Simular envio
  await new Promise(r => setTimeout(r, 2000))
  alert('Formulário enviado com sucesso!')
  enviando.value = false
  email.value = ''
  senha.value = ''
}
</script>

<template>
  <div class="formulario">
    <h2>Formulário de Cadastro</h2>
    
    <div>
      <label>Email:</label>
      <input v-model="email" type="email" />
      <p v-if="!emailValido" class="erro">Email inválido</p>
    </div>
    
    <div>
      <label>Senha:</label>
      <input v-model="senha" type="password" />
      <p v-if="senhaFraca" class="aviso">Senha muito fraca</p>
    </div>
    
    <button @click="enviar" :disabled="!formularioValido">
      {{ enviando ? 'Enviando...' : 'Enviar' }}
    </button>
  </div>
</template>

<style scoped>
.formulario {
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

div {
  margin-bottom: 15px;
}

label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

button {
  width: 100%;
  padding: 10px;
  background-color: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.erro {
  color: red;
  font-size: 0.9rem;
}

.aviso {
  color: orange;
  font-size: 0.9rem;
}
</style>

Exemplo 2: Lista Dinâmica

<script setup>
import { ref, computed } from 'vue'

const novoItem = ref('')
const itens = ref([])

const adicionar = () => {
  if (novoItem.value.trim()) {
    itens.value.push({
      texto: novoItem.value,
      concluido: false
    })
    novoItem.value = ''
  }
}

const remover = (index) => {
  itens.value.splice(index, 1)
}

const itensConcluidos = computed(() => {
  return itens.value.filter(item => item.concluido).length
})
</script>

<template>
  <div class="lista-app">
    <h2>Minhas Tarefas</h2>
    
    <div class="adicionar">
      <input
        v-model="novoItem"
        @keyup.enter="adicionar"
        placeholder="Adicione uma nova tarefa"
      />
      <button @click="adicionar">Adicionar</button>
    </div>
    
    <div v-if="itens.length === 0" class="vazio">
      Nenhuma tarefa ainda!
    </div>
    
    <ul v-else>
      <li
        v-for="(item, index) in itens"
        :key="index"
        :class="{ completo: item.concluido }"
      >
        <input
          type="checkbox"
          v-model="item.concluido"
        />
        <span>{{ item.texto }}</span>
        <button @click="remover(index)" class="btn-remover">
          ✕
        </button>
      </li>
    </ul>
    
    <p class="stats">
      {{ itensConcluidos }} de {{ itens.length }} tarefas completas
    </p>
  </div>
</template>

<style scoped>
.lista-app {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.adicionar {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

input[type="text"] {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

button {
  padding: 10px 20px;
  background-color: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.vazio {
  text-align: center;
  color: #999;
  padding: 20px;
}

ul {
  list-style: none;
  padding: 0;
}

li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
  border-bottom: 1px solid #eee;
}

li.completo span {
  text-decoration: line-through;
  color: #999;
}

.btn-remover {
  margin-left: auto;
  padding: 5px 10px;
  background-color: #ff6b6b;
  font-size: 0.9rem;
}

.stats {
  text-align: center;
  color: #666;
  margin-top: 20px;
}
</style>

5. Referências


6. Exercícios Propostos

Exercício 1: Gerador de Cores

Criar um componente que:

  • Exiba uma cor em hexadecimal
  • Tenha um botão "Gerar Cor Aleatória"
  • Mude a cor de fundo toda vez que clicar
  • Exiba o nome da cor (se possível) ou código RGB

Exercício 2: Inversor de Texto

Criar um componente que:

  • Tenha um input que receba texto
  • Mostre em tempo real o texto digitado normalmente
  • Mostre o texto invertido
  • Mostre estatísticas (caracteres, palavras)
  • Tenha um botão para copiar para clipboard

Exercício 3: Filtro de Produtos

Criar um componente com:

  • Uma lista de produtos (nome, preço, categoria)
  • Input de busca por nome
  • Seleção de categoria (todas, eletrônicos, roupas, etc)
  • Exibir produtos filtrados
  • Mostrar quantidade de produtos encontrados

Exercício 4: Menu com Abas

Criar um componente com:

  • Múltiplas abas (Sobre, Contato, Serviços)
  • Conteúdo diferente para cada aba
  • Indicador visual da aba ativa
  • Transição suave entre abas

Exercício 5: Calculadora Simples

Criar um calculadora que:

  • Tenha dois inputs (números)
  • Operações: +, -, *, /
  • Exiba o resultado
  • Trate divisão por zero
  • Limite a duas casas decimais

7. Checklist de Aprendizagem

  • Entendo interpolação básica com {{ }}
  • Consigo usar expressões JavaScript em templates
  • Domino v-if, v-else-if, v-else
  • Consigo renderizar listas com v-for
  • Entendo a importância da :key em listas
  • Consigo ouvir eventos com @
  • Domino binding de atributos com :
  • Consigo usar v-model em formulários
  • Consigo trabalhar com classes e estilos dinâmicos
  • Completei todos os 5 exercícios propostos