DEV Community

Cover image for Vue3: diretivas — cheat sheet (built-in e custom)
Angela Caldas
Angela Caldas

Posted on

Vue3: diretivas — cheat sheet (built-in e custom)

Uma das vantagens do Vue é a facilidade de manipular o DOM através de atributos especiais que chamamos de Diretivas. Além de ter várias delas embutidas, o Vue também permite que criemos diretivas personalizadas.

Se você está cansado de usar querySelector's e addEventListener's, então esse artigo é pra você!

Sumário

Quer ir direto ao ponto?
O que são diretivas?
Como criar diretivas customizadas
Criando diretivas customizadas locais
Importando uma diretiva customizada externa no componente
Declarando custom directives globalmente
Diretivas customizadas com modificadores
Ufa! Acho que vimos o suficiente...

Quer ver uma diretiva específica?

Gif de Wall-E


O que são diretivas?

As Diretivas nada mais são que atributos manipuladores de templates usadas nas tags do HTML. Com elas, podemos alterar dinamicamente nosso DOM, acrescentando ou omitindo informações e elementos (Leonardo Vilarinho em Front-end com Vue.js: Da teoria à prática sem complicações).

O Vue possui uma grande quantidade de diretivas embutidas. Pra saber mais como usá-las, vamos mostrar aqui alguns exemplos práticos pra cada uma delas (você também pode acessar diretamente alguns exemplos na documentação do Vue).

Voltar ao sumário


v-text

<h1 v-text="title"></h1>
<!-- é o mesmo que -->
<h1>{{ title }}</h1>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-text é usada para inserir um textContent em um elemento e funciona exatamente igual à interpolação com mustache ({{ }}). A diferença de uso entre eles é que o v-text vai substituir todo o textContent do elemento, e a interpolação permite que você substitua apenas partes do conteúdo.

Voltar ao sumário


v-html

<script setup>
import { ref } from "vue"
const myTitle = ref("<h1>Título</h1>")
</script>

<template>
  <div v-html="myTitle"></div>
</template>

<!--
O código acima vai renderizar:
<div>
  <h1>Título</h1>
</div>
 -->
Enter fullscreen mode Exit fullscreen mode

A diretiva v-html é usada para injetar um HTML dinâmico dentro de outro elemento (equivalente ao que acontece quando usamos innerHtml no JavaScript). É uma diretiva que deve ser usada com cautela, pois pode permitir ataques XSS (Cross-site Scripting), onde códigos maliciosos podem ser inseridos no DOM da sua aplicação.

Voltar ao sumário


v-show

<script setup>
import { ref } from "vue"
const visivel = ref(true)
</script>

<template>
  <div>
    <button @click="visivel = !visivel">Ocultar/Mostrar</button>
    <p v-show="visivel">Lorem ipsum</p>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-show altera o display do elemento dinamicamente dependendo do valor recebido. No exemplo acima, quando o estado visivel é true, o parágrafo recebe display: block (display padrão do elemento <p>). Ao clicar no botão, o estado visivel recebe vira false e atribui ao parágrafo o display: none.

Voltar ao sumário


v-if / v-else-if / v-else

<p v-if="type === 'A'">Um parágrafo</p>
<a v-else-if="type === 'B'" href="#">Um link</a>
<ErrorComponent v-else />
Enter fullscreen mode Exit fullscreen mode

Algumas das diretivas mais utilizadas no Vue, v-if, v-else-if e v-else são utilizadas para renderização dinâmica de componentes e elementos e seguem a mesma lógica de if, else if e else no JavaScript vanilla. No exemplo, se type for 'A', renderizamos um parágrafo; no entanto, se for 'B', renderizamos uma âncora; e em qualquer outro caso, renderizamos um componente chamado ErrorComponent.

Nota: Os elementos gerenciados através de v-if, v-else-if e v-else são totalmente destruídos ou reconstruídos no DOM de acordo com a condição alcançada, diferente do v-show que apenas muda o display do elemento.

Voltar ao sumário


v-for

<script setup>
import { ref } from "vue"
const users = ref([
  { name: 'John' },
  { name: 'Jane' }
])
</script>

<template>
  <p v-for="user in users">
    {{ user.name }}
  </p>
</template>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-for renderiza uma lista de elementos ou componentes ao iterar com um array ou um objeto, similar a um forEach. No nosso exemplo:

  • temos um array de objetos chamado users;
  • com a diretiva v-for, iteramos por esse array usando a sintaxe variavel in expressao para acessar cada elemento da iteração;
  • cada user será um objeto de nosso array. Dessa forma, teremos dois user, que resultarão em dois parágrafos;
  • cada parágrafo vai renderizar o valor da chave name de cada user.

Um ponto importante do v-for é que precisamos fornecer um atributo especial :key, que deve receber um valor único. Esse valor pode ser derivado diretamente do nosso user ou podemos utilizar o index do nosso array (que não é recomendado caso você precise manipular os itens do array, pois pode resultar em erros):

<!-- usando um valor único de 'user' -->
<p v-for="user in users" :key="user.name">
  {{ user.name }}
</p>

<!-- usando o índice do array -->
<p v-for="(user, index) in users" :key="index">
  {{ user.name }}
</p>
Enter fullscreen mode Exit fullscreen mode

Voltar ao sumário


v-on

<button v-on:click="handleClick">Clique</button>
<button @click="handleClick">Clique</button>

<input type="text" v-on:focus="handleFocus" />
<input type="text" @focus="handleFocus" />

<!-- eventos com modificadores -->
<button type="submit" @click.prevent="submit">Enviar</button>
<input @keyup.enter="handleEnterKey" />
Enter fullscreen mode Exit fullscreen mode

A diretiva v-on (ou apenas @, como atalho) adiciona um "escutador de eventos" aos elementos HTML, semelhante ao que faríamos com o addEventListener do JavaScript.

Qualquer evento padrão do JavaScript pode ser utilizado com a diretiva v-on, que também aceita modificadores de comportamentos e/ou modificadores de teclas.

No bloco de código acima, temos alguns exemplos de eventos, como v-on:click (ou @click) e v-on:focus (ou @focus), e também mostramos alguns eventos com modificadores, como .prevent (referente a event.preventDefault()) e .enter (que identifica a tecla "Enter" para o evento de keyup).

Voltar ao sumário


v-bind

<!-- Atributos dinâmicos -->
<img v-bind:src="imgSrc" />
<img :src="imgSrc" />
<img :src /> <!-- equivale a :src="src" -->

<!-- Classes dinâmicas -->
<div :class="myClasses"></div>
<div :class="{ myClass: isValid }"></div>
<!-- Resulta em <div class="red"></div> se `isRed` for truthy -->

<!-- Props para componentes-filhos -->
<ChildComponent :prop="myProp" />
Enter fullscreen mode Exit fullscreen mode

A diretiva v-bind é utilizada para criar/vincular atributos HTML dinâmicos aos elementos ou para enviar propriedades (props) para componentes-filhos. A diretiva v-bind pode ser substituída apenas por : como atalho.

Nos nossos exemplos, temos:

  • Um estado imgSrc utilizado como atributo src de uma imagem, bem como um estado myClasses utilizado como classe dinâmica;
  • A forma reduzida :src, para o caso do nome da variável ser igual ao nome do atributo;
  • Exemplo de atribuição dinâmica de uma classe "myClass" caso o estado isValid seja truthy.
  • Exemplo de uma prop myProp sendo passada para um componente-filho.

Voltar ao sumário


v-model

<script setup>
import { ref } from "vue"
const message = ref("")
</script>

<template>
  <input type="text" v-model="message" />
</template>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-model cria vínculos bidirecionais (two-way data binding), facilitando a sincronização de estados entre inputs, selects e componentes.

No exemplo acima, o estado message é vinculado a um <input> através da v-model, logo, quando digitarmos no input, o valor de message é automaticamente atualizado com o que foi digitado. Da mesma forma, se tivermos uma função que altere o valor de message, por exemplo, o input vai refletir essa alteração.

Podemos usar o v-model também para criar esse vínculo bidirecional de componente-pai para componente-filho:

<!-- passando props como somente leitura -->
<ChildComponent :msg="message" />

<!-- passando props com vínculo bidirecional -->
<ChildComponent v-model="message" />
<!-- ou -->
<ChildComponent v-model:msg="message" />
Enter fullscreen mode Exit fullscreen mode

Voltar ao sumário


v-slot

<!-- componente-filho com slots nomeados -->
<div>
  <slot name="title" />
  <slot name="message" />
</div>

<!-- componente-pai -->
<ComponenteFilho>
  <template v-slot:title>
    <h1>Meu título</h1>
  </template>
  <template #message>
    <p>Lorem ipsum</p>
  </template>
</ComponenteFilho>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-slot serve para definir e usar named slots em componentes. Slots são uma forma de passar conteúdo para um componente filho de forma mais flexível do que através de props e que podem ou não receber um nome para identificá-los, ajudando-o a inserir os elementos no componente-filho nos locais corretos.

No exemplo acima, temos um ComponenteFilho que consiste em uma div que engloba dois named slots: title e message. Ao usar o componente-filho, passamos dois elementos (h1 e p) para ele através de templates que utilizam a diretiva v-slot com o nome do slot que queremos que receba cada elemento. A diretiva v-slot pode ser abreviada com o símbolo #.

Voltar ao sumário


v-pre

<script setup>
import { ref } from "vue"
const message = ref("Uma mensagem qualquer")
</script>

<template>
  <p>{{ message }}</p>
  <p v-pre>{{ message }}</p> 
</template>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-pre ignora a compilação do elemento em que é usado, bem como todos os seus elementos-filhos, renderizando na tela o conteúdo que seria dinâmico como texto simples (o primeiro parágrafo renderizará Uma mensagem qualquer enquanto que o segundo parágrafo renderizará {{ message }}).

Voltar ao sumário


v-once

<div v-once>
  <h1>Comentário</h1>
  <p>{{msg}}</p>
</div>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-once auxilia em questões de performance fazendo com que o conteúdo de um elemento renderize apenas uma vez, se tornando estático logo em seguida. Acima, o parágrafo vai renderizar o estado msg apenas uma vez e permanecerá estático mesmo que o valor de msg mude posteriormente.

Voltar ao sumário


v-memo

<script setup>
import { ref, computed } from "vue"
const count = ref(0)
function calculate() {
  return // Lógica que utiliza `count`
}
</script>

<template>
  <div v-memo="[count]">
    {{ calculate() }}
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-memo é um pouco parecida com a v-once, porém ela limita a re-renderização do elemento ou componente a alterações em um ou mais estados, que devem ser passados como dependências da diretiva. No nosso exemplo, temos uma função calculate cujo resultado deve ser renderizado dentro da div. No entanto, essa re-renderização só deve ocorrer caso o valor de count seja atualizado, pois ele está referenciado na diretiva v-memo como dependência.

A diretiva v-memo salva o conteúdo em cache e só o atualiza se uma das suas dependências sofrer atualização. Exatamente o que acontece com as computed properties.

Essa diretiva serve para micro-otimizações de renderização, utilizada mais comumente em componentes mais complexos. No entanto, se a lógica do seu componente estiver seguindo as boas práticas, a necessidade de uso de v-memo se torna quase inexistente.

Por exemplo, se calculate fosse uma computed property, não precisaríamos de v-memo, pois as propriedades computadas fazem exatamente o que a diretiva faz: salva valores em cache e só as atualiza novamente quando as dependências mudam:

<script setup>
// código ocultado
const calculate = computed(() => {
  return // Lógica que utiliza `count`
})
</script>

<template>
  <div>{{ calculate }}</div>
</template>
Enter fullscreen mode Exit fullscreen mode

Voltar ao sumário


v-cloak

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- código ocultado -->
  <style>
    [v-cloak] {
      display: none;
    }
  </style>
</head>

<body>
  <div id="app">
    <p v-cloak>{{ message }}</p>
  </div>

  <script src="https://unpkg.com/vue@3"></script>
  <script>
    const app = Vue.createApp({
      data() {
        return {
          message: 'Hello, World!'
        }
      }
    })

    app.mount('#app')
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

A diretiva v-cloak evita que conteúdo não-compilado seja renderizado na tela até que o Vue finalize sua inicialização (o que normalmente acontece quando criamos uma aplicação Vue diretamente em um arquivo HTML via CDN. No exemplo acima, a diretiva v-cloak vai esconder o parágrafo até que o estado message seja inicializado e o componente esteja devidamente montado.

Voltar ao sumário


Como criar diretivas customizadas

Diretivas customizadas permitem vincular estados do Vue aos elementos HTML, manipulando-os às regras de negócio que sua aplicação possa ter. Dessa forma, você terá um maior controle do layout da aplicação.

Essas diretivas são definidas como um objeto que contém lifecycle hooks (os mesmos que usamos nos componentes), e cada hook recebe o elemento no qual a diretiva vai ser usada. A documentação do Vue oferece exemplos bem fáceis de entender.

Criando diretivas customizadas locais

<template>
  <input v-focus />
</template>

<!-- Options API -->
<script>
export default {
  directives: {
    focus: {
      mounted: (el) => el.focus()
    }
  }
}
</script>

<!-- Composition API -->
<script setup>
const vFocus = { mounted: (el) => el.focus() }
</script>
Enter fullscreen mode Exit fullscreen mode

Aqui temos uma diretiva customizada local chamada v-focus que foca em um input automaticamente quando o componente for montado. Com a Options API, precisamos declarar nossa diretiva dentro do objeto directives, mas na Composition API, basta criarmos uma variável (que deve iniciar obrigatoriamente com v).


Importando uma diretiva customizada externa no componente

Imagine que você precisa usar a diretiva v-focus em vários componentes. Isso geraria um monte de código repetido na sua aplicação, pois você teria que redeclarar a diretiva em todos os componentes em que pretende usar, correto?

Neo vs Smith clones

Vamos acabar com esses códigos repetidos.

Pra evitar essa repetição, podemos extrair a lógica da nossa nova diretiva para um arquivo na pasta directives:

// src/directives/v-focus.js
export const vFocus = {
  mounted: (el) => el.focus()
};
Enter fullscreen mode Exit fullscreen mode

Agora, é só importar a diretiva no componente desejado e usá-la:

<template>
  <input v-focus />
</template>

<!-- Options API -->
<script>
import { vFocus } from '@/directives/v-focus.js';

export default {
  directives: {
    focus: vFocus
  }
}
</script>

<!-- Composition API -->
<script setup>
import { vFocus } from '@/directives/v-focus.js';
</script>
Enter fullscreen mode Exit fullscreen mode

Declarando custom directives globalmente

Se você precisar usar demais uma determinada diretiva customizada, uma solução mais adequada pode ser declará-la globalmente no seu main.js ou main.ts:

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import { vFocus } from '@/directives/v-focus.js'

const app = createApp(App)

// Você pode importar a diretiva de um arquivo externo
app.directive('focus', vFocus)

// Ou você pode declará-la diretamente assim
app.directive('focus', {
  mounted: (el) => el.focus()
})

app.mount('#app')
Enter fullscreen mode Exit fullscreen mode

Voltar ao sumário


Diretivas customizadas com modificadores

Até agora, aprendemos a criar diretivas customizadas simples. Mas e se você quiser uma diretiva mais complexa, que tenha modificadores de ações, como @click.prevent?

Uma diretiva pode ter até quatro tipos de atributos que podem ser utilizados em sua declaração, sendo os mais importantes (e nosso foco nesse artigo) os seguintes:

  • el: O elemento no qual a diretiva está sendo usado (como vimos no vFocus; e
  • binding: Objeto contendo várias propriedades que podemos usar em nossas diretivas, como value (o valor passado na diretiva) e modifiers, que é o que usaremos para criar nossos modificadores.

Por exemplo, se tivermos a diretiva <div v-exemplo:foo.bar="um">, o nosso objeto binding seria:

{
  arg: 'foo',
  modifiers: { bar: true },
  value: 'um',
  oldValue: /* qualquer valor anterior que a diretiva teve */
}
Enter fullscreen mode Exit fullscreen mode

Vamos ver na prática como criar um diretiva para formatar o texto em letras maiúsculas, minúsculas ou capitalizadas.

1. Criamos a estrutura inicial da diretiva vFormatar, que vai executar ações quando o elemento for montado (mounted) no componente. Veja que estamos utilizando el e binding como parâmetros do nosso hook:

// src/directives/vFormatar.js
export const vFormatarTexto = {
  mounted: (el, binding) => {},
}
Enter fullscreen mode Exit fullscreen mode

2. Criaremos uma variável modifier que vai identificar o modificador utilizado na diretiva. Quando usamos um modificador em uma diretiva, eles são salvos em um objeto modifiers dentro do objeto binding. Então, se usarmos v-formatar.maiusculo, binding.modifiers será { maiusculo: true } e o valor da variável modifier será maiusculo:

// src/directives/vFormatar.js
export const vFormatarTexto = {
  mounted: (el, binding) => {
    const modifier = Object.keys(binding.modifiers)[0];
  },
}
Enter fullscreen mode Exit fullscreen mode

3. Agora criaremos a variável actions, que contém as funções formatadoras de texto da nossa diretiva. Vamos capturar o innerText do elemento onde a diretiva será usada e formataremos para maiúsculo, minúsculo ou capitalizado:

// src/directives/vFormatar.js
export const vFormatarTexto = {
  mounted: (el, binding) => {
    const modifier = Object.keys(binding.modifiers)[0];

    const actions = {
      maiusculo() {
        el.innerHTML = el.innerHTML.toUpperCase();
      },
      minusculo() {
        el.innerHTML = el.innerHTML.toLowerCase();
      },
      capitalizado() {
        const txt = el.innerHTML.split(" ");
        el.innerHTML = "";

        for (let i = 0; i < txt.length; i++) {
          el.innerHTML +=
            txt[i].substring(0, 1).toUpperCase() + txt[i].substring(1) + " ";
        }
      },
    };
  },
}
Enter fullscreen mode Exit fullscreen mode

4. Por último, vamos identificar e executar a função que corresponde ao modificador utilizado:

// src/directives/vFormatar.js
export const vFormatarTexto = {
  mounted: (el, binding) => {
    const modifier = Object.keys(binding.modifiers)[0];

    const actions = {
      maiusculo() {
        el.innerHTML = el.innerHTML.toUpperCase();
      },
      minusculo() {
        el.innerHTML = el.innerHTML.toLowerCase();
      },
      capitalizado() {
        const txt = el.innerHTML.split(" ");
        el.innerHTML = "";

        for (let i = 0; i < txt.length; i++) {
          el.innerHTML +=
            txt[i].substring(0, 1).toUpperCase() + txt[i].substring(1) + " ";
        }
      },
    };

    if (modifier in actions) {
      const action = actions[modifier];
      action();
    }
  },
}
Enter fullscreen mode Exit fullscreen mode

Prontinho! Nossa diretiva v-formatar está completa e já pode ser utilizada em um componente. Vamos a um exemplo:

<script setup>
import { vFormatar } from "@/directives/vFormatar.js"
</script>

<template>
  <p v-formatar.maiusculo>Meu texto</p> <!-- "MEU TEXTO" -->
  <p v-formatar.minusculo>Meu texto</p> <!-- "meu texto" -->
  <p v-formatar.capitalizado>Meu texto</p> <!-- "Meu Texto" -->
</template>
Enter fullscreen mode Exit fullscreen mode

E que tal ver como seria essa diretiva com a Options API?

// src/directives/vFormatar.js
export default {
  mounted: function(el, binding) {
    const modifier = Object.keys(binding.modifiers)[0]
    const actions = {
      maiusculo() {
        el.innerHTML = el.innerHTML.toUpperCase()
      },
      minusculo() {
        el.innerHTML = el.innerHTML.toLowerCase()
      },
      capitalizado() {
        let txt = el.innerHTML.split(' ')
        el.innerHTML = ''

        for (let i = 0; i < txt.length; i++) {
          el.innerHTML += txt[i].substring(0, 1).toUpperCase() + txt[i].substring(1) + ' '
        }
      },
    }

    if(modifier in actions) {
      const action = actions[modifier]
      action()
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Utilizando a diretiva em um componente:

<script>
import vFormatar from "@/directives/vFormatar"

export default {
  directives: { formatar: vFormatar }
}
</script>

<template>
  <p v-formatar.maiusculo>Meu texto</p> <!-- "MEU TEXTO" -->
  <p v-formatar.minusculo>Meu texto</p> <!-- "meu texto" -->
  <p v-formatar.capitalizado>Meu texto</p> <!-- "Meu Texto" -->
</template>
Enter fullscreen mode Exit fullscreen mode

E sempre lembrando que você também pode registrar a diretiva de forma global no arquivo main.js:

import vFormatar from "./directives/vFormatar";

const app = createApp(App)
app.directive('formatar', vFormatar)
app.mount('#app')
Enter fullscreen mode Exit fullscreen mode

Voltar ao sumário


Ufa! Acho que vimos o suficiente...

Criar diretivas customizadas mais completas pode parecer um pouco confuso num primeiro momento, mas nada que a prática não resolva!

Saber como usar as diretivas built-in do Vue vai ser essencial pra que sua aplicação Vue tenha sempre uma ótima performance ao lidar com renderizações de componentes e manipulação de informações no DOM, além de serem uma mão na roda pra sua experiência como desenvolvedor, deixando seu trabalho mais fácil e seu código mais elegante.

Porém, nunca diga nunca, jovem padawan. Em situações mais complexas, você pode perceber que um querySelector de vez em quando ainda pode quebrar um bom galho!

Espero que este artigo seja útil. Nos vemos na próxima. Um xero!

Top comments (0)