Skip to content

Modelo Virtual do Componente

Uso Básico

A v-model pode ser usada num componente para implementar um vínculo bidirecional.

Desde a Vue 3.4, a abordagem recomendada para alcançar isto é usar a macro defineModel():

vue
<!-- Child.vue -->
<script setup>
const model = defineModel()

function update() {
  model.value++
}
</script>

<template>
  <div>Parent bound v-model is: {{ model }}</div>
</template>

O pai pode então vincular um valor com v-model:

template
<!-- Parent.vue -->
<Child v-model="countModel" />

O valor retornado por defineModel() é uma referência. Esta pode ser acessada e alterada como qualquer outra referência, exceto que comporta-se como um vínculo bidirecional entre um valor pai e um valor local:

  • Seu .value é sincronizado com o valor vinculado pela v-model do pai;
  • Quando é alterada pelo filho, faz com que o valor vinculado ao pai também seja atualizado.

Isto significa que também podemos vincular esta referência a um elemento de entrada nativo com v-model, simplificando o embrulhar de elementos de entrada nativos enquanto fornecemos o mesmo uso de v-model:

vue
<script setup>
const model = defineModel()
</script>

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

Exemplo de Testes

Nos Bastidores

A defineModel é uma macro de conveniência. O compilador expande-a ao seguinte:

  • Uma propriedade com o nome de modelValue, com a qual o valor da referência local é sincronizado;
  • Um evento com o nome de update:modelValue. que é emitido quando o valor da referência local é alterado.

É assim que implementaríamos o mesmo componente filho mostrado acima antes da versão 3.4:

vue
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

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

Como podemos ver, é um pouco mais verboso. No entanto, é útil para compreender o que acontece nos bastidores.

Uma vez que defineModel declara uma propriedade, podemos então declarar as opções da propriedade subjacente passando-a a defineModel:

js
// tornar o `v-model` obrigatório
const model = defineModel({ required: true })

// fornecer um valor padrão
const model = defineModel({ default: 0 })

AVISO

Se tivermos um valor default para a propriedade defineModel e não fornecermos nenhum valor para esta propriedade do componente pai, isto pode causar uma dessincronização entre os componentes pai e filho. No exemplo abaixo, a myRef do componente pai é undefined, mas model do componente filho é 1:

js
// componente filho:
const model = defineModel({ default: 1 })

// componente pai:
const myRef = ref()
html
<Child v-model="myRef"></Child>

Primeiro, revisitaremos como a v-model é usada sobre um elemento nativo:

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

Nos bastidores, o compilador do modelo de marcação expande v-model ao 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 usada sobre um componente, v-model expande-se para isto:

template
<CustomInput
  :model-value="searchText"
  @update:model-value="newValue => searchText = newValue"
/>

Para que isto realmente funcione, o componente <CustomInput> deve fazer duas coisas:

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

Eis o que se passa 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>

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

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

Experimentar na Zona de Testes

Uma outra maneira de implementar a v-model dentro deste componente é usar uma propriedade computed gravável com 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>

Argumentos da v-model

A v-model sobre um componente também pode aceitar um argumento:

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

No componente filho, podemos suportar o argumento correspondente passando uma sequência de caracteres à defineModel() como seu primeiro argumento:

vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>

<template>
  <input type="text" v-model="title" />
</template>

Experimentar na Zona de Testes

Se as opções de propriedade também forem necessárias, devem ser passadas depois do nome do modelo:

js
const title = defineModel('title', { required: true })
Uso antes da 3.4
vue
<!-- MyComponent.vue -->
<script setup>
defineModel({
  title: {
    required: true
  }
})
defineEmits(['update:title'])
</script>

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

Experimentar na Zona de Testes

Neste caso, ao invés da propriedade modelValue e evento update:modelValue padrão, o componente filho deve esperar uma propriedade title e emitir um evento update:title para atualizar o valor do pai:

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>

Experimentar na Zona de Testes

Vários Vínculos de v-model

Com o aproveitamento da capacidade de mirar uma propriedade e um evento em particular, como aprendemos anteriormente com os argumentos da v-model, podemos agora criar vários vínculos de v-model numa única instância de componente.

Cada v-model sincronizar-se-á com uma propriedade diferente, sem a necessidade de opções adicionais no componente:

template
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

Experimentar na Zona de Testes

Uso antes da 3.4
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>

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

Experimentar na Zona de Testes

Manipulação de Modificadores da v-model

Quando aprendemos sobre os vínculos de entrada de formulário, vimos que a v-model tem modificadores embutidos.trim, .number e .lazy. Em alguns casos, também podemos querer que a v-model sobre o nosso componente de entrada personalizado suporte modificadores personalizados.

Criaremos um exemplo de modificador personalizado, capitalize, que transforma a primeira letra da sequência de caracteres fornecida pelo vínculo de v-model em maiúscula:

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

Os modificadores adicionados a v-model de um componente podem ser acessados no componente filho através da desestruturação do valor de retorno da defineModel() da seguinte maneira:

vue
<script setup>
const [model, modifiers] = defineModel()

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

<template>
  <input type="text" v-model="model" />
</template>

Para ajustar condicionalmente como o valor de ser lido ou escrito baseado nos modificadores, podemos passar as opções get e set a defineModel(). Estas duas opções recebem o valor na recuperação ou definição da referência do modelo e devem retornar um valor transformado. É assim que podemos usar a opção set para implementar o modificador capitalize:

vue
<script setup>
const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})
</script>

<template>
  <input type="text" v-model="model" />
</template>

Experimentar na Zona de Testes

Uso antes da 3.4
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>

Experimentar na Zona de Testes

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

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>

Observemos que a propriedade modelModifiers do componente contém capitalize e seu valor é true — devido ao fato de ter sido definida sobre o vínculo da v-model v-model.capitalize="myText".

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

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>

Experimentar na Zona de Testes

Modificadores para v-model com Argumentos

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

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

As declarações correspondentes devem ser:

js
export default {
  props: ['title', 'titleModifiers'],
  emits: ['update:title'],
  created() {
    console.log(this.titleModifiers) // { capitalize: true }
  }
}

Eis um outro exemplo de uso de modificadores com várias v-model com diferentes argumentos:

template
<UserName
  v-model:first-name.capitalize="first"
  v-model:last-name.uppercase="last"
/>
vue
<script setup>
const [firstName, firstNameModifiers] = defineModel('firstName')
const [lastName, lastNameModifiers] = defineModel('lastName')

console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true}
</script>
Uso antes da 3.4
vue
<script setup>
const props = defineProps({
firstName: String,
lastName: String,
firstNameModifiers: { default: () => ({}) },
lastNameModifiers: { default: () => ({}) }
})
defineEmits(['update:firstName', 'update:lastName'])

console.log(props.firstNameModifiers) // { capitalize: true }
console.log(props.lastNameModifiers) // { uppercase: true}
</script>
vue
<script>
export default {
  props: {
    firstName: String,
    lastName: String,
    firstNameModifiers: {
      default: () => ({})
    },
    lastNameModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:firstName', 'update:lastName'],
  created() {
    console.log(this.firstNameModifiers) // { capitalize: true }
    console.log(this.lastNameModifiers) // { uppercase: true}
  }
}
</script>
Modelo Virtual do Componente has loaded