How to use Vue 3 new defineModel macro

defineModel (Vue 3.4) simplifies working with multiple bindings between components using v-model


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:

Child.vueVue
<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

Child.vueVue
<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.

Parent.vueVue
<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:

JS
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:

Child.vueVue
<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.

Child.vueVue
<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).

Child.vueVue
<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.

Documentation

Demo

Juan Andrés Núñez
Juan Andrés Núñez
Frontend Engineer. Vue.js Specialist. Professional Teacher. Stoic.