¿Cómo usar defineExpose?

Dentro de un componente que utiliza <script setup>, defineExpose() es una macro del compilador que permite exponer de forma explícita variables, funciones o incluso refs para que puedan ser accesibles desde el exterior. Esto es útil, por ejemplo, cuando queremos controlar un componente a través de una referencia (ref) en el componente padre.

Ejemplo Práctico: Componente Modal

Imagina que quieres crear un componente modal que se pueda abrir y cerrar desde un componente padre. Para ello, definiremos dos métodos: open y close, que manipulan un estado reactivo isOpen que controla la visibilidad del modal. Con defineExpose() haremos que estos métodos sean accesibles desde el exterior.

Código del componente Modal

<script setup lang="ts">
  import { ref } from 'vue'

  const isOpen = ref(false)

  const open = () => {
    isOpen.value = true
  }

  const close = () => {
    isOpen.value = false
  }

  defineExpose({
    open,
    close
  })
</script>

<template>
  <div
    v-if="isOpen"
    class="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-75"
  >
    <div class="bg-white p-6 rounded-lg shadow-lg">
      <slot />
      <button
        class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-700"
        @click="close"
      >
        Cerrar
      </button>
    </div>
  </div>
</template>

Explicación del Código

  1. Plantilla (Template):
    Se utiliza v-if="isOpen" para mostrar u ocultar el modal según el estado de la variable visible. Además, se define un slot para permitir la inserción de contenido personalizado y se añade un botón que cierra el modal al hacer clic.

  2. Script Setup:

    • Se importa ref de Vue para crear una variable reactiva isOpen.

    • Se definen dos funciones, open y close, que modifican el estado del modal.

    • Se llama a defineExpose({ open, close }) para que estos métodos sean accesibles desde el componente padre a través de una referencia.

Uso desde el Componente Padre

Para utilizar este modal, podrías tener un componente padre que lo importe y lo controle mediante una referencia:

<script setup lang="ts">
  import { useTemplateRef } from 'vue'
  import MyModal from '@/components/MyModal.vue'

  const myModal = useTemplateRef('myModalRef')

  const handleOpenModal = () => {
    myModal.value?.open()
  }
</script>

<template>
  <main class="flex items-center justify-center flex-col gap-4 h-screen">
    <button @click="handleOpenModal">
      Abrir modal
    </button>

    <MyModal ref="myModalRef" />
  </main>
</template>

En este ejemplo, al hacer clic en el botón "Abrir Modal", se invoca el método open() expuesto desde el componente Modal, lo que cambia el estado de isOpen y muestra el modal.

Conclusión

El uso de defineExpose() en Vue 3 con <script setup> es una herramienta poderosa que permite mantener el encapsulamiento del componente sin sacrificar la flexibilidad de exponer ciertas funcionalidades a componentes padres. Con este enfoque, es posible crear componentes modulares y reutilizables, como en el ejemplo del modal, donde los métodos open y close se pueden invocar externamente para controlar su comportamiento.