Component v-model
Basic Usage
Section titled “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:
<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
:
<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
.value
is 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:
<script setup>const model = defineModel();</script>
<template> <input v-model="model" /></template>
Under the Hood
Section titled “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:
<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:
<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 requiredconst model = defineModel({ required: true });
// providing a default valueconst model = defineModel({ default: 0 });
v-model Arguments
Section titled “v-model Arguments”v-model
on a component can also accept an argument:
<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:
<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:
const title = defineModel("title", { required: true });
Pre 3.4 Usage
<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
Section titled “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:
<UserName v-model:first-name="first" v-model:last-name="last" />
<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
<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
Section titled “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:
<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:
<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:
<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
<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
Section titled “Modifiers for v-model with Arguments”Here’s another example of using modifiers with multiple v-model
with different arguments:
<UserName v-model:first-name.capitalize="first" v-model:last-name.uppercase="last"/>
<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
<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>