v-model can be used on a component to implement a two-way binding.
Starting in Vue 3.4, the recommended approach to achieve this is using the defineModel() macro:
<scriptsetup>
constmodel=defineModel()
functionupdate() {
model.value++
}
</script>
<template>
<div>Parent bound v-model is: {{ model }}</div>
<button@click="update">Increment</button>
</template>
The parent can then bind a value with v-model:
<Childv-model="countModel" />
The value returned by defineModel() is a ref. It can be accessed and mutated like any other ref, except that it acts as a two-way binding between a parent value and a local one:
Its .value is synced with the value bound by the parent v-model;
When it is mutated by the child, it causes the parent bound value to be updated as well.
This means you can also bind this ref to a native input element with v-model, making it straightforward to wrap native input elements while providing the same v-model usage:
Then, v-model="foo" in the parent component will be compiled to:
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>
As you can see, it is quite a bit more verbose. However, it is helpful to understand what is happening under the hood.
Because defineModel declares a prop, you can therefore declare the underlying prop’s options by passing it to defineModel:
// making the v-model required
constmodel=defineModel({ required: true })
// providing a default value
constmodel=defineModel({ default: 0 })
[!caution]
If you have a default value for defineModel prop and you don’t provide any value for this prop from the parent component, it can cause a de-synchronization between parent and child components. In the example below, the parent’s myRef is undefined, but the child’s model is 1:
By leveraging the ability to target a particular prop and event as we learned before with v-model arguments, we can now create multiple v-model bindings on a single component instance.
Each v-model will sync to a different prop, without the need for extra options in the component:
When we were learning about form input bindings, we saw that v-model has built-in modifiers - .trim, .number and .lazy. In some cases, you might also want the v-model on your custom input component to support custom modifiers.
Let’s create an example custom modifier, capitalize, that capitalizes the first letter of the string provided by the v-model binding:
<MyComponentv-model.capitalize="myText" />
Modifiers added to a component v-model can be accessed in the child component by destructuring the defineModel() return value like this:
<scriptsetup>
const [model, modifiers] =defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<inputtype="text"v-model="model" />
</template>
To conditionally adjust how the value should be read / written based on modifiers, we can pass get and set options to defineModel(). These two options receive the value on get / set of the model ref and should return a transformed value. This is how we can use the set option to implement the capitalize modifier: