# Diretivas Customizadas

# Introdução

Em adição ao conjunto de diretivas existente por padrão em seu núcleo (como v-model ou v-show), o Vue te possibilita registrar diretivas customizadas. Note que no Vue, a forma primária de reuso e abstração de código são os componentes - no entanto, pode haver casos em que você precise um acesso de nível mais baixo aos elementos no DOM, e é aqui que as diretivas customizadas são úteis. Um exemplo seria acionar o foco em um elemento input, como esse:

Veja o exemplo Diretivas customizadas: exemplo básico por vuejs-br (@vuejs-br) no CodePen.

Quando a página carrega, o elemento ganha o foco (nota: o atributo nativo autofocus não funciona no Safari para dispositivos móveis). Na verdade, se você não clicou em nada desde que visitou essa página, o input acima deve estar focado agora. Além disso, você pode clicar no botão Rerun e o input vai ganhar foco.

Agora vamos construir a diretiva que faz isso:

const app = Vue.createApp({})
// Registra uma diretiva customizada global chamada `v-focus`
app.directive('focus', {
  // Quando o elemento vinculado é inserido no DOM
  mounted(el) {
    // Foca o elemento
    el.focus()
  }
})
1
2
3
4
5
6
7
8
9

Se você deseja registrar uma diretiva local em vez disso, os componentes também aceitam a opção directives:

directives: {
  focus: {
    // definição da diretiva
    mounted(el) {
      el.focus()
    }
  }
}
1
2
3
4
5
6
7
8

Então no template, você pode usar o novo atributo v-focus em qualquer elemento, por exemplo:

<input v-focus />
1

# Funções de Gatilhos (Hook Functions)

As funções de hook são as funções que são executadas conforme o estado da diretiva e há uma variedade disponível para uso (todas opcionais):

  • created: Executa antes que os atributos do elemento vinculado ou ouvintes de evento sejam aplicados. Isso é útil nos casos em que a diretiva precisa anexar ouvintes de eventos que devem ser chamados antes dos ouvintes de eventos v-on normais.

  • beforeMount: Executa quando a diretiva é ligada pela primeira vez ao elemento e antes que o componente pai seja montado. Aí é onde você pode fazer o trabalho de configuração inicial.

  • mounted: Executa antes que o componente pai do elemento vinculado é montado.

  • beforeUpdate: Executa antes que o VNode contido no componente seja atualizado.

Nota

Vamos cobrir VNodes com mais detalhes depois, quando discutirmos as funções de renderização (render functions).

  • updated: Executado após o VNode contido no componente e os VNodes de seus filhos terem sido atualizados.

  • beforeUnmount: Executado antes do pai dos elementos ligados sejam desmontados.

  • unmounted: Executado apenas uma vez, quando a diretiva é desligada do elemento e o componente pai é desmontado

Você pode checar os argumentos que foram passados para esses hooks (ex: el, binding, vnode e prevNode) em API de Diretivas Customizadas

# Argumentos Dinâmicos de Diretiva

Os argumentos da diretiva podem ser dinâmicos. Por exemplo, em v-mydirective:[argument]="value", o argument pode ser atualizado baseada nas propriedades de dados na nossa instância do componente! Isso faz nossas diretivas customizadas flexíveis para utilizar na aplicação.

Digamos que você queira fazer uma diretiva customizada que permite "pregar" elementos na sua página utilizando posicionamento fixo. Nós poderiamos criar uma diretiva customizada onde o valor atualiza a posição vertical em pixels, desse jeito:

<div id="dynamic-arguments-example" class="demo">
  <p>Role para baixo</p>
  <p v-pin="200">Me pregue 200px do topo da página</p>
</div>
1
2
3
4
const app = Vue.createApp({})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.value é o valor que vamos passar para a diretiva - nesse caso, é 200.
    el.style.top = binding.value + 'px'
  }
})

app.mount('#dynamic-arguments-example')
1
2
3
4
5
6
7
8
9
10
11

Isso iria pregar o elemento 200px do topo da página. Mas o que acontece quando encontramos um cenário que precisamos pregar um elemento da esquerda, ao invés do topo? É aqui que os argumentos dinâmicos que podem ser atualizados por instância de componente são úteis:

<div id="dynamicexample">
  <h3>Role para baixo nessa seção ↓</h3>
  <p v-pin:[direction]="200">Estou pregado na página 200px à esquerda.</p>
</div>
1
2
3
4
const app = Vue.createApp({
  data() {
    return {
      direction: 'right'
    }
  }
})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.arg é um argumento que passamos para a diretiva
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})

app.mount('#exemplo-argumentos-dinamicos')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Resultado:

Veja o exemplo Diretivas customizadas: argumentos dinâmicos por vuejs-br (@vuejs-br) no CodePen.

A nossa diretiva customizada agora está flexível o suficiente para atender a vários casos diferentes. Para deixá-lo mais dinâmico, podemos possibilitar alterar um valor vinculado. Vamos criar uma propriedade adicional pinPadding e vincular ao <input type="range">




 


<div id="dynamicexample">
  <h2>Role a página para baixo</h2>
  <input type="range" min="0" max="500" v-model="pinPadding">
  <p v-pin:[direction]="pinPadding">Me pregue {{ pinPadding + 'px' }} da {{ direction || 'top' }} da página</p>
</div>
1
2
3
4
5




 




const app = Vue.createApp({
  data() {
    return {
      direction: 'right',
      pinPadding: 200
    }
  }
})
1
2
3
4
5
6
7
8

Agora vamos incrementar a lógica da diretiva para recalcular a distância dos elementos pregados quando atualizar o componente:







 
 
 
 


app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  },
  updated(el, binding) {
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})
1
2
3
4
5
6
7
8
9
10
11

Resultado:

Veja o exemplo Diretivas customizadas: argumentos dinâmicos + vínculo dinâmico por vuejs-br (@vuejs-br) no CodePen.

# Atalho da Função (Function Shorthand)

No exemplo anterior, você pode desejar o mesmo comportamento no mounted e updated, mas não liga para os outros gatilhos. Você pode fazer isso passando a callback para a diretiva:

app.directive('pin', (el, binding) => {
  el.style.position = 'fixed'
  const s = binding.arg || 'top'
  el.style[s] = binding.value + 'px'
})
1
2
3
4
5

# Objetos Literais (Object Literals)

Se a sua diretiva precisa de múltiplos valores, você também pode passar um objeto literal do javascript. Lembre-se que as diretivas pode receber qualquer expressão Javascript válida.

<div v-demo="{ color: 'white', text: 'olá!!' }"></div>
1
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "olá!"
})
1
2
3
4

# Uso em Componentes

Quando usada em componentes, a diretiva customizada sempre se aplicará ao nó raiz do componente, de forma semelhante a atributos não-prop.

<my-component v-demo="test"></my-component>
1
app.component('my-component', {
  template: `
    <div> // a diretiva v-demo será aplicada aqui
      <span>Conteúdo do meu componente</span>
    </div>
  `
})
1
2
3
4
5
6
7

Ao contrário dos atributos, as diretivas não podem ser passadas para um elemento diferente com v-bind="$attrs".

Com o suporte de fragmentos, os componentes podem ter potencialmente mais de um nó raiz. Quando aplicada a um componente de múltiplas raízes, a diretiva será ignorada e o aviso será lançado.