Comprendiendo la Vinculación Bidireccional en Vue3 con defineModel

En Vue 3.4, defineModel se ha convertido en una macro oficial que simplifica la vinculación bidireccional entre componentes padre e hijo. Esta característica reduce significativamente el código necesario al encapsular la lógica de sincronización.

Método atnerior a Vue 3.4

Antes de defineModel, se requería definir una prop modelValue y emitir un evento update:modelValue manualmente. Ejemplo de componente padre:

<template>
  <MiCampoTexto v-model="nombreEntrada" />
</template>

<script setup>
import { ref } from 'vue';
const nombreEntrada = ref('');
</script>

Componente hijo:

<template>
  <input
    :value="propiedades.valorModelo"
    @input="emitir('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
const propiedades = defineProps(['valorModelo']);
const emitir = defineEmits(['update:modelValue']);
</script>

Este enfoque mantiene el flujo de datos unidireccional, pero es verboso. El componente hijo no puede modificar directamente la prop; en su lugar, debe emitir eventos.

Uso de defineModel

defineModel es una macro que no requiere importación. Devuelve una ref que representa el valor vinculado. Ejemplo simplificado:

Componente padre (igual al anterior):

<template>
  <MiCampoTexto v-model="nombreEntrada" />
</template>

<script setup>
import { ref } from 'vue';
const nombreEntrada = ref('');
</script>

Componente hijo:

<template>
  <input v-model="campo" />
</template>

<script setup>
const campo = defineModel();
campo.value = 'valor inicial';
</script>

Aquí, campo es una ref que se sincroniza directamente con el valor del padre. Modificar campo actualiza automáticamente nombreEntrada en el componente padre.

Principio de funcionamiento

Internamente, defineModel crea una ref local y una prop modelValue. Utiliza watchers para sincronizar ambos. Equivalente manual:

<template>
  <input v-model="campo" />
</template>

<script setup>
import { ref, watch } from 'vue';

const propiedades = defineProps(['valorModelo']);
const emitir = defineEmits(['update:modelValue']);
const campo = ref(propiedades.valorModelo);

watch(() => propiedades.valorModelo, (nuevoValor) => {
  campo.value = nuevoValor;
});

watch(campo, (nuevoValor) => {
  emitir('update:modelValue', nuevoValor);
});
</script>

Esto demuestra que el flujo de datos sigue siendo unidireccional: las props no se modifican directamente. defineModel abstrae esta lógica para mayor comodidad.

Definición de tipos y valores por defecto

Se pueden especificar opciones de prop, como tipo y valor predeterminado:

const campo = defineModel({ type: String, default: 'texto predeterminado' });

Otras opcoines como required y validator también son compatibles.

Múltiples vinculaciones v-model

Para múltiples v-model, se pasa un nombre como primer argumento:

const primerCampo = defineModel('campoUno');
const segundoCampo = defineModel('campoDos');

En el componente padre:

<MiComponente v-model:campoUno="valorA" v-model:campoDos="valorB" />

Cada vinculación puede tener opciones independientes:

const primerCampo = defineModel('campoUno', { type: Number, default: 0 });

Modificadores

Los modificadores integrados como trim funcionan directamente:

<MiCampoTexto v-model.trim="nombreEntrada" />

Para modificadores personalizados, como convertir texto a mayúsculas, se usa la estructura con set:

<template>
  <input v-model="valorModelo" />
</template>

<script setup>
const [valorModelo, modificadores] = defineModel({
  set(nuevoValor) {
    if (modificadores.mayusculas) {
      return nuevoValor?.toUpperCase();
    }
    return nuevoValor;
  }
});
</script>

El componente padre aplicaría el modificador así:

<MiCampoTexto v-model.trim.mayusculas="nombreEntrada" />

Al desestructurar, modificadores es un objeto como { trim: true, mayusculas: true }, lo que permite lógica condicional en el setter.

Esta característica hace que defineModel sea una herramienta poderosa para gestionar la vinculación bidireccional de manera declarativa en Vue 3.

Etiquetas: Vue3 defineModel vinculación bidireccional macros Vue composables

Publicado el 7-9 07:14