Cómo usar la macro defineModel de Vue 3

defineModel (Vue 3.4) simplifica el trabajo con múltiples bindings entre componentes usando v-model


Hasta ahora, conectar de forma reactiva tus componentes con v-model era un proceso un tanto manual. Tienes que definir las propiedades junto a los eventos, y no olvidarte de emitirlos usando la sintaxis correcta.

Un ejemplo sencillo:

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

Desde Vue 3.4, tenemos acceso al macro defineModel, que simplifica el proceso sobremanera. Por ejemplo, podemos replicar el ejemplo anterior usando solo defineModel.

Child.vueVue
<script setup>
  const model = defineModel();
</script>
<template>
  <input v-model="model" />
</template>

defineModel se encarga por nosotros de crear la propiedad modelValue, el evento update:modelValue y unirlo todo en compile time. Por lo que al final el código resultante es parecido al primero ejemplo.

La propiedad que creamos es una referencia (ref) reactiva común, que está sincronizada en dos direcciones: padre e hijo.

Parent.vueVue
<script setup>
  ...
  const data = ref('defineModel rocks');
</script>
<template>
  <Child v-model="data" />
</template>

Aún al hacerlo a través de una macro, estamos declarando una propiedad, así que también podemos especificar las opciones más usuales:

JS
const model = defineModel({ default: 'Foo', required: true, type: String });

Más de un v-model

Si quieres usar más de un v-model tendrás que utilizar un identificador a través de un argumento: v-model:rating, por ejemplo. En este caso, basta con usar el identificador al usar la macro.

Repitamos el mismo ejemplo:

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>

Cada propiedad pasada con v-model al componente hijo, se sincroniza con el padre como de costumbre, pero al usar defineModel todo el proceso es menos 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>

Modificadores de v-model

En caso de que quieras crear tu propio modificador de v-model, puedes seguir usando defineModel. Supongamos que quieres añadir el modificador reverse para darle la vuelta a valor de v-model. Como podrás imaginar el proceso es mucho más sencillo, ya que no tenemos que crear una función que aplique la transformación que buscamos. defineModel se encarga de todo.

En el componente hijo ya basta con que desestructures su resultado y uses los métodos get y set (como en las propiedades computadas).

Child.vueVue
<script setup>
  const [title, titleModifiers] = defineModel('title', {
    set(value) {
      if (titleModifiers.reverse) {
        return value.split('').reverse('').join('');
      }
      return value;
    },
  });
  ...
</script>

Conclusión

La nueva macro defineModel puede parecer trivial, pero cualquiera que tenga que trabajar con múltiples bindings entre componentes sabrá lo tedioso que es mantener todo actualizado.

De esta forma e igual que con la sintaxis setup, Vue nos hace la vida un poco más sencilla.

Documentation

Demo

Juan Andrés Núñez
Juan Andrés Núñez
Ingeniero Frontend. Especialista en Vue.js. Docente profesional. Estoico.