Skip to content
On this page

O Modelo Virtual do Componente

A v-model pode ser usada sobre um componente para implementar uma vínculo de duas vias.

Primeiro vamos revisitar como a v-model é usada sobre um elemento nativo:

template
<input v-model="searchText" />

Nos bastidores, o compilador do modelo de marcação expande a v-model para o equivalente mais verboso por nós. Então o código acima faz o mesmo que o seguinte:

template
<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

Quando usado sobre um componente, a v-model expande para isto:

template
<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

Mas para isto realmente funcionar, o componente <CustomInput> deve fazer duas coisas:

  1. Vincular o atributo value de um elemento <input> nativo à propriedade modelValue
  2. Quando um evento input nativo é acionado, emite um evento update:modelValue personalizado com o novo valor

Cá está aquilo em ação:

vue
<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
vue
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

Agora a v-model deve funcionar perfeitamente com este componente:

template
<CustomInput v-model="searchText" />

Um outra maneira de implementar a v-model dentro deste componente é usar uma propriedade computed graváveis com ambos um recuperador e um definidor. O método get deve retornar a propriedade modelValue e o método set deve emitir o evento correspondente:

vue
<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  }
}
</script>

<template>
  <input v-model="value" />
</template>
vue
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

<template>
  <input v-model="value" />
</template>

Argumentos de v-model

Por padrão, a v-model em um componente usa modelValue como a propriedade e o update:modelValue como o evento. Nós podemos modificar estes nomes passando um argumento para v-model:

template
<MyComponent v-model:title="bookTitle" />

Neste caso, o componente filho deve esperar uma propriedade title e emitir um evento update:title para atualizar o valor do componente pai:

vue
<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

Experimente-o na Zona de Testes

vue
<!-- MyComponent.vue -->
<script>
export default {
  props: ['title'],
  emits: ['update:title']
}
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

Experimente-o na Zona de Testes

Várias Vinculações de v-model

Ao influenciar a habilidade de escolher uma propriedade e evento em particular como alvo conforme aprendemos antes com os argumentos de v-model, podemos agora criar várias vinculações de v-model sobre uma única instância de componente.

Cada v-model se sincronizará à uma propriedade diferente, sem a necessidade para opções adicionais em um componente:

template
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
vue
<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

Experimente-o na Zona de Testes

vue
<script>
export default {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName']
}
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

Experimente-o na Zona de Testes

Manipulando Modificadores de v-model

Quando estávamos a aprender a respeito das vinculações de entrada do formulário, vimos que a v-model tem modificadores embutidos - .trim, .number, e .lazy. Em alguns casos, também podes querer que a v-model no teu componente de entrada personalizado suportar modificadores personalizados.

Vamos criar um exemplo de modificador personalizado, capitalize, que escreve com maiúsculas a primeira letra da sequência de caracteres fornecidos pelo vinculo da v-model:

template
<MyComponent v-model.capitalize="myText" />

Os modificadores adicionados para uma v-model do componente serão fornecidos ao componente através da propriedade modelModifiers. No exemplo abaixo, criamos um componente que contém de uma propriedade modelModifiers que predefine para um objeto vazio:

vue
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

defineEmits(['update:modelValue'])

console.log(props.modelModifiers) // { capitalize: true }
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
vue
<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  }
}
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

Repara que a propriedade modelModifiers do componente contém capitalize e seu valor é true - devido a ela estar definida no v-model.capitalize="myText" do vinculo da v-model.

Agora que temos a nossa propriedade definida, podemos verificar as chaves do objeto modelModifiers e escrever um manipulador para mudar o valor emitido. No código abaixo escreveremos com maiúsculas a primeira letra da sequência de caracteres sempre que o elemento <input /> disparar um evento de input.

vue
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])

function emitValue(e) {
  let value = e.target.value
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  emit('update:modelValue', value)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

Experimente-o na Zona de Testes

vue
<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

Experimente-o na Zona de Testes

Para os vínculos de v-model com ambos argumento e modificadores, o nome da propriedade gerada será arg + "Modifiers". Por exemplo:

template
<MyComponent v-model:title.capitalize="myText">

As declarações correspondente devem ser:

js
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }
js
export default {
  props: ['title', 'titleModifiers'],
  emits: ['update:title'],
  created() {
    console.log(this.titleModifiers) // { capitalize: true }
  }
}
O Modelo Virtual do Componente has loaded