Reutilização de Código
Tags: vue
Published: 2020-02-19 12:55:22

##1 - Introdução

Antes de falar sobre as técnicas para reutilização de código, vamos apresentar um problema para servir de exemplo:

No Exemplo abaixo, temos um QInput com uma mascara dinâmica, no caso, uma mascara para telefone.

{% codepen https://codepen.io/tobias-mesquita/pen/MWweMod default-tab=result %}

Ou seja, temos uma propriedade computada para montar a mascara, e um watch, que é basicamente um workaround para resolver um problema de usabilidade.

Se por um acaso, precisamos adicionar mais um campo com telefone, temos que duplicar a propriedade computada e o watch.

{% codepen https://codepen.io/tobias-mesquita/pen/NWqrZwO default-tab=result %}

Ou seja, não deve ser nada produtivo, e nem pratico para manter este código.

##2 - Componente Utilitário

Acredito que você deva ter uma pasta cheia de funções utilitárias (`utils`, `helpers`, `services`, etc). Mas, e se no lugar de temos funções, nós tivéssemos componentes? Sim, é isto que iremos fazer aqui.

Um componente utilitário é basicamente o inverso de um componente funcional, ele deve manipular os dados, manter a reatividade, mas não pode renderizar nada que altere a estética do front.

Aqui segue um componente utilitário para resolver o problema da mascara.:

```js
export default {
  name: 'MaskPhoneProviderComponent',
  props: {
    value: String
  },
  watch: {
    mask () {
      let input = this.$children[0].$refs.input
      requestAnimationFrame(() => {
        input.selectionStart = input.value.length
      })
    }
  },
  computed: {
    mask () {
      switch (true) {
        case (this.value || '').length <= 8: return '####-#####'
        case (this.value || '').length === 9: return '#####-#####'
        case (this.value || '').length === 10: return '(##) ####-#####'
        default: return '(##) #####-####'
      }
    }
  },
  render (h) {
    return this.$scopedSlots.default({ mask: this.mask })[0]
  }
}
```

E agora, um exemplo de como utiliza-lo
```js
Vue.component('mask-phone-provider', () => import('components/MaskPhoneProvider.js'))
```
```html
<mask-phone-provider :value="landline" v-slot="{ mask }">
  <q-input v-model="landline" unmasked-value :mask="mask" outlined dark label="Tel. Fixo"></q-input>
</mask-phone-provider>
<mask-phone-provider :value="cellphone" v-slot="{ mask }">
  <q-input v-model="cellphone" unmasked-value :mask="mask" outlined dark label="Celular"></q-input>
</mask-phone-provider>
```

E claro, não poderia deixar vocês sem um codepen completamente funcional:

{% codepen https://codepen.io/tobias-mesquita/pen/YzXWoeQ default-tab=result %}

##3 - Transparent Wrappers

Transparent Wrappers, é basicamente um novo componente envelopando o componente original (no caso um QInput), passando os seus atributos, propriedades e [scoped] slots, e de brinde, adicionando novas funcionalidades

Agora iremos implementar um Transparent Wrapper para resolver o caso acima.:

```html
<q-input ref="root" v-bind="$attrs" v-on="$listeners" unmasked-value :mask="mask" v-model="__value">
  <slot v-for="(slot, key) in $slots" :name="key" :slot="key" />
  <template v-for="(slot, key) in $scopedSlots" :slot="key" slot-scope="scope">
    <slot :name="key" v-bind="scope"/>
  </template>
</q-input>
```
```js
export default {
  name: 'QPhoneInput',
  props: {
    value: String
  },
  watch: {
    mask () {
      let input = this.$refs.root.$refs.input
      requestAnimationFrame(() => {
        input.selectionStart = input.value.length
      })
    }
  },
  computed: {
    mask () {
      switch (true) {
        case (this.value || '').length <= 8: return '####-#####'
        case (this.value || '').length === 9: return '#####-#####'
        case (this.value || '').length === 10: return '(##) ####-#####'
        default: return '(##) #####-####'
      }
    },
    __value: {
      get () { return this.value },
      set (value) { this.$emit('input', value) }
    }
  }
}
```

Agora, um exemplo de como utilizar este transparent wrapper no código.:

```js
Vue.component('q-phone-input', import('components/QPhoneInput'))
```
```html
<q-phone-input v-model="landline" ref="landline" outlined dark label="Tel. Fixo"></q-phone-input>
<q-phone-input v-model="cellphone" ref="cellphone" outlined dark label="Celular"></q-phone-input>
```

E como de um praxe, um codepen:

{% codepen https://codepen.io/tobias-mesquita/pen/PoqzMEv default-tab=result %}

##4 - TobyMosque Utility Belt

Transparent Wrappers como implementados acima, vem com um custo, eles não repassam tudo para o componente original, por exemplo, a propriedade `key` e os `methods` não podem ser acessados diretamente:

por exemplo, o seguinte código resultaria em erro:

```html
<div class="q-gutter-sm">
  <q-phone-input v-model="landline" ref="landline" outlined dark label="Tel. Fixo"></q-phone-input>
  <q-phone-input ref="cellphone" v-model="cellphone" ref="cellphone" outlined dark label="Celular"></q-phone-input>
</div>
```
```
export default {
  data () {
    return {
      landline: '',
      cellphone: ''
    }
  },
  mounted () {
    this.$refs.cellphone.focus()
  }
}
```

Isto ocorre, por que `focus` é um método do `QInput` e não do `QPhoneInput`, uma forma de fazer o código acima funcionar, seria:

```js
this.$refs.cellphone.$refs.root.focus()
```

nada pratico, não é?

Para resolver este problema, eu criei uma extensão com alguns utilitários, um deles é responsável por criar Transparent Wrappers completos.

** Coming Soon **