DEV Community

Tobias Mesquita for Quasar Framework Brasil

Posted on • Updated on

Reutilização de Código

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.

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.

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.:

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

Vue.component('mask-phone-provider', () => import('components/MaskPhoneProvider.js'))
<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:

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.:

<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>
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.:

Vue.component('q-phone-input', import('components/QPhoneInput'))
<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:

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:

<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:

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 **

Discussion (1)

Collapse
garbinmarcelo profile image
Marcelo Garbin

Boaaaa @tobias , essa parte de Transparent Wrappers me ajudou muito, obrigado por compartilhar! Abç