É muito comum utilizar bibliotecas de componentes para facilitar o nosso trabalho do dia a dia, elas costumam ser extremamente dinâmicas, com um layout padronizado e testadas pela comunidade, mas oque acontece quando voce precisa de uma funcionalidade que não foi implementada? Esse foi um problema que aconteceu comigo recentemente e creio que ja tenha ocorrido com voce também.

Em um projeto estávamos utilizando a biblioteca Vuetify como base do nosso layout de formulários, e em vários locais na aplicação era nescessário utilizar o input de valores monetários, na época, o único plugin que existia para isso era o v-money ele fornece um componente de input que funciona perfeitamente, e uma diretiva que era oque eu queria utilizar ja que precisava manter o v-text-field que já estava aplicado no app inteiro. Foi ai que surgiu o problema, quando fui testar ele como diretiva por algum motivo interno ele converte o meu model para String, me forçando a fazer parse do valor para decimal em todos os lugares, gerando muitos pontos de possíveis erros e manutenção futura.

A solução que decidimos? Criar o nosso próprio componente de currency encapsulando o v-text-field em um componente que chamamos de currency-field. A idéia era bem simples capturar qualquer input gerado no v-text-field formatar ele no currency padrão do sistema( $ 0.000,00 ) e devolver o valor em decimal no model do componente. Para não ter que lidar com a parte de formatação eu utilizei a lib Numeral.js, vamos dar uma olhada no código nescessário para isso:

<template>
  <v-text-field ref="currencyField" v-model="currencyFieldValue">
  </v-text-field>
</template>

<script>
import numeral from 'numeral'

export default {
  name: 'CurrencyField',
  props: ['value', 'currencyPrefix'],
  data() {
    return {
      fieldValue: null,
      prefix: '$',
    }
  },
  created() {
    this.fieldValue = this.value
    this.prefix = this.currencyPrefix || '$'
  },
  computed: {
    currencyFieldValue: {
      get() {
        return this.fieldValue == null ?
          "" :
          numeral(this.fieldValue).format(`${this.prefix}0,0.00`)
      },
      set(newValue) {
        this.currencyFieldValueChanged(newValue)
      }
    }
  },
  watch: {
    value(newValue) {
      if(newValue == this.fieldValue) return

      this.fieldValue = newValue
      this.updateViewValue(newValue)
    },
  },
  methods: {
    currencyFieldValueChanged(newVal) {
      if(newVal == this.currencyFieldValue) return
      if (!newVal.length) {
        this.updateModelValue(null)
        return
      }

      let newParsedValue = parseFloat(newVal.replace(/\D/gi, '')) / 100
      this.updateModelValue(newParsedValue)
    },
    updateViewValue() {
      let el = this.$refs.currencyField.$el.getElementsByTagName('input')[0]
      el.value = this.currencyFieldValue
      el.dispatchEvent(this.createEvent('input'))
    },
    updateModelValue(modelValue) {
      this.fieldValue = modelValue
      this.$emit('input', modelValue)
      this.updateViewValue()
    },
    createEvent (name) {
      var evt = document.createEvent('Event')
      evt.initEvent(name, true, true)
      return evt
    }
  }
}
</script>

Os pontos de atenção neste código são 2, a entrada e saida de valores no componente, e a entrada e saida de valores no nosso v-text-field que estamos encapsulando, no text-field utilizamos uma variavel computed com setter e getter como v-model, no get aplicamos a nossa máscara para para mostrar o dado formatado e no set pegamos o input do usuário. Já para a entrada e saída de valores do nosso componente, nós damos watch na prop value caso o valor tenho sido alterado externamente, e executamos this.$emit(‘input’, modelValue) para informar quem estiver escutando o v-model ou o @input do nosso currency-field.

Com isso nosso componente está pronto, porem ficamos com um problema, e todas as props que tenho no v-text-field? E se eu quiser fazer uma validação com a prop rule, definir um label ou placeholder? É ai que as novas funcionalidades do ES6 vem a calhar, utilizando o Spread Operator podemos linkar todas as props e attrs passadas para o nnosso componente diretamente para o v-text-field

<template>
  <v-text-field v-bind="{...$props, ...$attrs}" ref="currencyField" v-model="currencyFieldValue">
  </v-text-field>
</template>

Espero que tenham gostado da solução para esse problema, o componente em sí ainda possui muito espaço para melhorias, uma forma de configuração global ou registrar novos formatters do numeral.js ou até mesmo transformar ele em um proxy para o numeral.js e permitir que o usuário passe a máscara na qual ele quer formatar os números.