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?
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).
v-text
<h1 v-text="title"></h1>
<!-- é o mesmo que -->
<h1>{{ title }}</h1>
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.
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>
-->
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.
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>
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
.
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 />
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
ev-else
são totalmente destruídos ou reconstruídos no DOM de acordo com a condição alcançada, diferente dov-show
que apenas muda odisplay
do elemento.
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>
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 sintaxevariavel in expressao
para acessar cada elemento da iteração; - cada
user
será um objeto de nosso array. Dessa forma, teremos doisuser
, que resultarão em dois parágrafos; - cada parágrafo vai renderizar o valor da chave
name
de cadauser
.
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>
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" />
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
).
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" />
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 atributosrc
de uma imagem, bem como um estadomyClasses
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
sejatruthy
. - Exemplo de uma prop
myProp
sendo passada para um componente-filho.
v-model
<script setup>
import { ref } from "vue"
const message = ref("")
</script>
<template>
<input type="text" v-model="message" />
</template>
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" />
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>
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 #
.
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>
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 }}
).
v-once
<div v-once>
<h1>Comentário</h1>
<p>{{msg}}</p>
</div>
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.
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>
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>
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>
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.
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>
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?
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()
};
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>
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')
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 novFocus
; e -
binding
: Objeto contendo várias propriedades que podemos usar em nossas diretivas, comovalue
(o valor passado na diretiva) emodifiers
, 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 */
}
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) => {},
}
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];
},
}
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) + " ";
}
},
};
},
}
4. Por último, vamos identificar o modificador e executar a função que corresponde a ele:
// 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();
}
},
}
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>
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()
}
}
}
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>
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')
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)