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 pelav-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>
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>
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>
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>
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>
Modificadores para v-model
com Argumentos
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>