Component v-model
Basic Usage
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:
vue
<script setup> const model = defineModel() function update() { 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:
template
<Child v-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
.valueis synced with the value bound by the parentv-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:
vue
<script setup> const model = defineModel() </script> <template> <input v-model="model" /> </template>Under the Hood
defineModel is a convenience macro. The compiler expands it to the following:
- A prop named
modelValue, which the local ref's value is synced with; - An event named
update:modelValue, which is emitted when the local ref's value is mutated.
This is how you would implement the same child component shown above prior to 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>Then, v-model="foo" in the parent component will be compiled to:
template
<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:
js
// making the v-model required const model = defineModel({ required: true }) // providing a default value const model = defineModel({ default: 0 })WARNING
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:
vue
<script setup> const model = defineModel({ default: 1 }) </script>vue
<script setup> const myRef = ref() </script> <template> <Child v-model="myRef"></Child> </template>v-model Arguments
v-model on a component can also accept an argument:
template
<MyComponent v-model:title="bookTitle" />In the child component, we can support the corresponding argument by passing a string to defineModel() as its first argument:
vue
<script setup> const title = defineModel('title') </script> <template> <input type="text" v-model="title" /> </template>If prop options are also needed, they should be passed after the model name:
js
const title = defineModel('title', { required: true })Pre 3.4 Usage
vue
<script setup> defineProps({ title: { required: true } }) defineEmits(['update:title']) </script> <template> <input type="text" :value="title" @input="$emit('update:title', $event.target.value)" /> </template>Multiple v-model Bindings
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:
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>Pre 3.4 Usage
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>Handling v-model Modifiers
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:
template
<MyComponent v-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:
vue
<script setup> const [model, modifiers] = defineModel() console.log(modifiers) // { capitalize: true } </script> <template> <input type="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:
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>Pre 3.4 Usage
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="props.modelValue" @input="emitValue" /> </template>Modifiers for v-model with Arguments
Here's another example of using modifiers with multiple v-model with different arguments:
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>Pre 3.4 Usage
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>