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