Objetivo y requisitos
Un componente con <script setup> mantiene su implementación privada. defineExpose permite publicar una API pequeña para que el padre la use a través de una referencia. Vamos a exponer open y close desde un modal. El ejemplo necesita Vue 3.5 o superior por useTemplateRef y un navegador compatible con el elemento HTML dialog.
MyModal.vue completo
Guárdalo en src/components/MyModal.vue. Usamos el diálogo nativo para obtener el comportamiento modal, el cierre con Escape y el manejo de foco del navegador.
<script setup lang="ts">
import { useTemplateRef } from 'vue'
const dialog = useTemplateRef<HTMLDialogElement>('dialog')
function open() {
if (dialog.value && !dialog.value.open) dialog.value.showModal()
}
function close() {
dialog.value?.close()
}
defineExpose({ open, close })
</script>
<template>
<dialog ref="dialog" aria-labelledby="modal-title">
<h2 id="modal-title">Un modal reutilizable</h2>
<slot />
<button type="button" autofocus @click="close">Cerrar</button>
</dialog>
</template>
<style scoped>
dialog { max-width: 32rem; padding: 2rem; border: 0; border-radius: 1rem; }
dialog::backdrop { background: rgb(0 0 0 / 60%); }
</style>
La referencia a dialog permanece dentro del componente. El padre solo puede llamar a los dos métodos publicados. defineExpose es una macro y no necesita un import.
Uso completo desde App.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import MyModal from './components/MyModal.vue'
const modal = useTemplateRef<InstanceType<typeof MyModal>>('modal')
</script>
<template>
<button type="button" @click="modal?.open()">Abrir modal</button>
<MyModal ref="modal">
<p>Este contenido llega desde el componente padre.</p>
</MyModal>
</template>
Comprueba el resultado
Ejecuta npm run dev. Al pulsar «Abrir modal» aparece el diálogo y el foco se mueve dentro. Ciérralo con el botón y también con Escape; comprueba que puedes volver a abrirlo y que el foco regresa al botón de apertura. Recorre la interfaz con Tab. Ejecuta npm run build para comprobar los tipos y la compilación.
Cuándo usarlo y qué evitar
Este patrón es útil para operaciones imperativas como enfocar un input o abrir un diálogo. Para datos y estado compartido, suele ser más claro usar props, eventos o v-model. No expongas todo el estado interno solo porque puedas hacerlo: el contrato pequeño facilita cambiar la implementación después.
La referencia puede ser null antes de montar el hijo o cuando un v-if lo retira. Usa el acceso opcional y llama a defineExpose antes de un await. Si usas Vue anterior a 3.5, sustituye useTemplateRef por una referencia declarada con ref y conectada al mismo nombre de plantilla.
Consulta defineExpose y las referencias de plantilla en la documentación de Vue.
Sigue leyendo
Una idea lleva a otra.
)
)
)
)