Componentes no Vue.js: Comunicação, Slots e Animações

Criando Componentes Vue

Um componante Vue é definido em um único arquivo com extensão .vue, contendo template, script e estilos.

Criando e Registrando um Componente

Crie o arquivo src/components/Saudacao.vue:

<template>
  <div class="saudacao">
    <p>{{ mensagem }}</p>
  </div>
</template>

<script>
export default {
  name: "Saudacao",
  data() {
    return {
      mensagem: "Olá, bem-vindo!"
    }
  }
}
</script>

<style scoped>
.saudacao {
  font-size: 1.2em;
  color: #2c3e50;
}
</style>

O atributo scoped garante que os estilos afetem apenas este componnete.

Para utilizar no src/App.vue:

<template>
  <div id="app">
    <Saudacao />
  </div>
</template>

<script>
import Saudacao from "./components/Saudacao"

export default {
  name: 'App',
  components: {
    Saudacao
  }
}
</script>

Passagem de Dados do Pai para o Filho (Props)

O componente pai envia dados ao filho através de atributos no template. O componente filho declara essas propreidades via props.

Exemplo Prático

Crie src/components/CardProduto.vue:

<template>
  <div class="card">
    <h3>{{ titulo }}</h3>
    <span>Código: {{ codigo }}</span>
    <p>Descrição: {{ descricao }}</p>
    <p>Preço: R$ {{ preco }}</p>
    <p>Categoria: {{ categoria }}</p>
  </div>
</template>

<script>
export default {
  name: 'CardProduto',
  props: {
    titulo: {
      type: String,
      required: true
    },
    codigo: {
      type: [String, Number],
      required: true
    },
    descricao: String,
    preco: Number,
    categoria: {
      type: String,
      default: "Geral"
    }
  }
}
</script>

Crie src/components/Vitrine.vue:

<template>
  <div class="vitrine">
    <h2>Vitrine de Produtos</h2>
    <CardProduto
      :codigo="produtoAtual.codigo"
      :titulo="produtoAtual.nome"
      :preco="produtoAtual.valor"
      :descricao="produtoAtual.desc"
    />
  </div>
</template>

<script>
import CardProduto from "./CardProduto"

export default {
  name: 'Vitrine',
  components: {
    CardProduto
  },
  data() {
    return {
      produtoAtual: {
        codigo: 101,
        nome: "Teclado Mecânico",
        valor: 250,
        desc: "Switches Cherry MX Blue"
      }
    }
  }
}
</script>

Comunicação do Filho para o Pai ($emit)

O componente filho dispara eventos customizados usando this.$emit(). O pai escuta esses eventos com v-on ou @.

Exemplo Prático

Crie src/components/BotaoAcao.vue:

<template>
  <div class="botao-acao">
    <button @click="enviarDados">Enviar para o Pai</button>
  </div>
</template>

<script>
export default {
  name: 'BotaoAcao',
  data() {
    return {
      mensagemInterna: "Dados do componente filho"
    }
  },
  methods: {
    enviarDados() {
      this.$emit("receberMensagem", this.mensagemInterna)
    }
  }
}
</script>

Crie src/components/PainelControle.vue:

<template>
  <div class="painel">
    <h2>Painel de Controle</h2>
    <BotaoAcao @receberMensagem="processarMensagem" />
    <p v-if="mensagemRecebida">Recebido: {{ mensagemRecebida }}</p>
  </div>
</template>

<script>
import BotaoAcao from "./BotaoAcao"

export default {
  name: 'PainelControle',
  components: {
    BotaoAcao
  },
  data() {
    return {
      mensagemRecebida: null
    }
  },
  methods: {
    processarMensagem(dados) {
      this.mensagemRecebida = dados
    }
  }
}
</script>

Slots no Vue

Slots permitem que o componente pai insira conteúdo dentro da estrutura do componente filho.

Slot Padrão

No componente filho, utilize <slot> como espaço reservado:

<template>
  <div class="caixa-conteudo">
    <h3>Cabeçalho fixo</h3>
    <slot>Conteúdo padrão aqui</slot>
  </div>
</template>

Slots Nomeados

Permite múltiplos pontos de inserção de conteúdo:

<template>
  <div class="layout">
    <header>
      <slot name="cabecalho">Header padrão</slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="rodape">Footer padrão</slot>
    </footer>
  </div>
</template>

Slots com Escopo (Scoped Slots)

Permite que o componente filho exponha dados para o pai renderizar:

Crie src/components/ListaDados.vue:

<template>
  <div class="lista-dados">
    <slot name="item" v-bind:registro="registroAtual"></slot>
  </div>
</template>

<script>
export default {
  name: 'ListaDados',
  data() {
    return {
      registroAtual: {
        nome: "Maria Silva",
        idade: 28,
        profissao: "Desenvolvedora"
      }
    }
  }
}
</script>

Uso no pai:

<ListaDados>
  <template v-slot:item="escopo">
    <p>{{ escopo.registro.nome }} - {{ escopo.registro.profissao }}</p>
  </template>
</ListaDados>

Cache de Componentes com Keep-Alive

O <keep-alive> preserva o estado dos componentes quando alternamos entre eles.

Exemplo com Alternância de Componentes

Crie src/components/PerfilUsuario.vue:

<template>
  <div class="perfil">
    <h3>Perfil do Usuário</h3>
    <input v-model="nome" placeholder="Digite seu nome" />
    <p>Nome digitado: {{ nome }}</p>
  </div>
</template>

<script>
export default {
  name: 'PerfilUsuario',
  data() {
    return {
      nome: ''
    }
  }
}
</script>

Crie src/components/Configuracoes.vue:

<template>
  <div class="config">
    <h3>Configurações</h3>
    <p>Painel de configurações do sistema</p>
  </div>
</template>

<script>
export default {
  name: 'Configuracoes'
}
</script>

No src/App.vue:

<template>
  <div id="app">
    <button @click="alternar">Alternar View</button>

    <!-- Sem cache -->
    <div>
      <component :is="viewAtual"></component>
    </div>

    <!-- Com cache -->
    <keep-alive>
      <component :is="viewAtual"></component>
    </keep-alive>
  </div>
</template>

<script>
import PerfilUsuario from "./components/PerfilUsuario"
import Configuracoes from "./components/Configuracoes"

export default {
  name: 'App',
  components: {
    PerfilUsuario,
    Configuracoes
  },
  data() {
    return {
      viewAtual: PerfilUsuario
    }
  },
  methods: {
    alternar() {
      this.viewAtual = this.viewAtual === PerfilUsuario
        ? Configuracoes
        : PerfilUsuario
    }
  }
}
</script>

Ciclo de Vida do Componente

Vue oferece hooks em diferentes fases do ciclo de vida:

  • Criação: beforeCreate()created()
  • Montagem: beforeMount()mounted()
  • Atualização: beforeUpdate()updated()
  • Destruição: beforeDestroy()destroyed()

Exemplo Demonstrando os Hooks

Crie src/components/MonitorEstado.vue:

<template>
  <div class="monitor">
    <h3>Monitor de Estado</h3>
    <p>Contador: {{ contador }}</p>
    <button @click="incrementar">+1</button>
  </div>
</template>

<script>
export default {
  name: 'MonitorEstado',
  data() {
    return {
      contador: 0
    }
  },
  methods: {
    incrementar() {
      this.contador++
    }
  },
  beforeCreate() {
    console.log('[Hook] Antes de criar')
  },
  created() {
    console.log('[Hook] Após criar')
  },
  beforeMount() {
    console.log('[Hook] Antes de montar no DOM')
  },
  mounted() {
    console.log('[Hook] Montado no DOM')
  },
  beforeUpdate() {
    console.log('[Hook] Antes da atualização')
  },
  updated() {
    console.log('[Hook] Após atualização')
  },
  beforeDestroy() {
    console.log('[Hook] Antes de destruir')
  },
  destroyed() {
    console.log('[Hook] Destruído')
  }
}
</script>

Animações e Transições

Transições CSS

Utilize o componente <transition> para animar elementos:

<template>
  <div class="animacao-demo">
    <button @click="visivel = !visivel">Alternar</button>
    <transition name="fade">
      <p v-if="visivel">Conteúdo animado!</p>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'AnimacaoDemo',
  data() {
    return {
      visivel: true
    }
  }
}
</script>

<style>
.fade-enter-active, .fade-leave-active {
  transition: all 0.6s ease;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}
</style>

Classes de Transição

  • Entrada: fade-enter (início), fade-enter-active (durante), fade-enter-to (fim)
  • Saída: fade-leave (início), fade-leave-active (durante), fade-leave-to (fim)

Usando Bibliotecas de Animação (Animate.css)

Adicione o CSS no index.html:

<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">

No template, especifique as classes de animação:

<transition
  enter-active-class="animated slideInLeft"
  leave-active-class="animated slideOutRight"
>
  <p v-if="visivel">Com Animate.css!</p>
</transition>

Diretivas Customizadas

Diretiva Global

Declare em src/main.js:

import Vue from 'vue'
import App from './App'

Vue.directive('destaque', {
  inserted(el, binding) {
    el.style.backgroundColor = binding.value || '#ffffcc'
  }
})

new Vue({
  el: '#app',
  render: h => h(App)
})

Diretiva Local

export default {
  name: 'MeuComponente',
  directives: {
    maiusculo: {
      inserted(el) {
        el.style.textTransform = 'uppercase'
      }
    }
  }
}

Uso no template:

<input v-destaque="'#ffe0b2'" />
<p v-maiusculo>este texto ficará em maiúsculas</p>

Filtros Customizados

Filtro Global

Declare em src/main.js:

Vue.filter('formatarMoeda', function(valor) {
  return 'R$ ' + parseFloat(valor).toFixed(2)
})

Filtro Local

export default {
  name: 'MeuComponente',
  data() {
    return {
      titulo: 'vue componentes'
    }
  },
  filters: {
    capitalizar(valor) {
      return valor.charAt(0).toUpperCase() + valor.slice(1)
    }
  }
}

Uso no template:

<p>{{ 1500 | formatarMoeda }}</p>
<p>{{ titulo | capitalizar }}</p>

Tags: Vue.js componentes props Slots diretivas

Publicado em 6-26 19:42