So far, reactively connecting your components with v-model
was a —somewhat— manual process. You have to define the properties alongside the events, and not forget emit them using a very specific syntax.
A simple example:
<script setup>
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
/>
</template>
✋ defineModel
As of Vue 3.4, we have access to the macro defineModel
, which greatly simplifies the process. For instance, we can replicate the above example using only defineModel
<script setup>
const model = defineModel();
</script>
<template>
<input v-model="model" />
</template>
What a difference.
As you can see, defineModel
takes care of creating the modelValue
property, update:modelValue
event, and tying everything together at compile time. So, in the end, the resulting code is similar to the first example, but without our manual intervention.
The property we create is a common two-way reactive reference (ref
) that is synced: parent and child.
<script setup>
...
const data = ref('defineModel rocks');
</script>
<template>
<Child v-model="data" />
</template>
Remember, even when doing it through a macro, we are declaring a component property, so we can also specify the usual options:
const model = defineModel({ default: 'Foo', required: true, type: String });
More than one v-model
If you want to use more than one v-model
you will have to use an identifier through an argument: v-model:rating
, for example. In this case, it is enough to use the identifier when using the macro.
Let's continue with the same example:
<script setup>
...
const title = ref('defineModel rocks');
const rating = ref(5);
</script>
<template>
<Child v-model:title="title" v-model:rating="rating" />
</template>
Each property passed with v-model to the child component is synced with the parent as usual, but using defineModel
makes the whole process less verbose.
<script setup>
const model = defineModel();
const rating = defineModel('rating', { required: true });
</script>
<template>
<input type="text" v-model="model" />
<input type="number" v-model="rating" />
</template>
v-model
modifiers
In case you want to create your own v-model
modifier, you can still use defineModel
. Suppose you want to add the reverse
modifier to reverse the value of v-model
. As you can imagine the process is much simpler, as we don't have to create a function that applies the transformation we are looking for. defineModel
takes care of everything.
In the child component, you just need to destructure its result and use the get
and set
methods (as in computed properties).
<script setup>
const [title, titleModifiers] = defineModel('title', {
set(value) {
if (titleModifiers.reverse) {
return value.split('').reverse('').join('');
}
return value;
},
});
...
</script>
Conclusion
The new defineModel
macro may seem trivial, but anyone who has to work with multiple bindings between components will know how tedious it is to keep everything updated.
In this way, and just like with the setup
syntax, Vue makes our lives a little bit easier.