Skip to content

04. Props y Eventos

En Vue 3 con la Composition API, la comunicación entre componentes se realiza principalmente mediante props (de padre a hijo) y eventos (de hijo a padre). El script setup introduce macros de compilador como defineProps y defineEmits que simplifican esta comunicación.

La macro defineProps() permite declarar las propiedades que un componente puede recibir de su componente padre. Esta macro está disponible automáticamente dentro de <script setup> sin necesidad de importarla.

Declaración básica de props
<script setup>
// Declaración simple de props
const props = defineProps(['title', 'message'])
// Acceso a las props
console.log(props.title)
console.log(props.message)
</script>
  • Es una macro de compilador disponible solo en <script setup>
  • No necesita ser importada
  • Devuelve un objeto reactivo con las propiedades recibidas
  • Se puede usar con sintaxis de array o de objeto para validación
  • Tiene soporte completo para TypeScript

Vue 3 ofrece excelente soporte para TypeScript, permitiendo tipar las props de manera precisa con la Composition API.

Tipado con genéricos
<script setup lang="ts">
// Usando type para definir la estructura
type Props = {
title: string
message?: string
count: number
items: string[]
callback: (id: number) => void
}
// Pasando el tipo como genérico
const props = defineProps<Props>()
</script>

Vue 3 permite validar las props y definir valores por defecto usando la sintaxis de objeto.

Validación completa de props
<script setup>
const props = defineProps({
// Tipo básico
title: String,
// Múltiples tipos permitidos
id: [String, Number],
// Prop requerida
message: {
type: String,
required: true
},
// Con valor por defecto
count: {
type: Number,
default: 0
},
// Objeto con valor por defecto (debe ser una función)
user: {
type: Object,
default: () => ({ name: 'Anónimo' })
},
// Array con valor por defecto (debe ser una función)
items: {
type: Array,
default: () => []
},
// Función validadora personalizada
price: {
type: Number,
validator: (value) => value >= 0
}
})
</script>

La macro defineEmits() permite declarar los eventos que un componente puede emitir hacia su componente padre. Al igual que defineProps(), está disponible automáticamente dentro de <script setup>.

Declaración básica de eventos
<script setup>
// Declaración simple de eventos
const emit = defineEmits(['update', 'delete'])
// Emitir eventos
function updateItem() {
emit('update', { id: 1, name: 'Updated Item' })
}
function deleteItem() {
emit('delete', 1) // Emitir con el ID como payload
}
</script>
  • Es una macro de compilador disponible solo en <script setup>
  • No necesita ser importada
  • Devuelve una función para emitir eventos
  • Se puede usar con sintaxis de array o de objeto para validación
  • Tiene soporte completo para TypeScript

Los eventos también pueden ser tipados con TypeScript para mejorar la seguridad de tipos.

Tipado de eventos
<script setup lang="ts">
// Definición de tipos para eventos
type Events = {
'update': [payload: { id: number, name: string }]
'delete': [id: number]
'select': [items: string[]]
'close': []
}
// Usando el tipo genérico
const emit = defineEmits<Events>()
// Uso con tipo seguro
function updateItem() {
// TypeScript verificará que el payload sea correcto
emit('update', { id: 1, name: 'Updated Item' })
}
</script>

Los eventos personalizados son fundamentales para la comunicación de hijo a padre en Vue 3.

Componente hijo (emisor)
<template>
<div>
<h2>{{ title }}</h2>
<button @click="sendUpdate">Actualizar</button>
<button @click="$emit('delete')">Eliminar</button>
</div>
</template>
<script setup>
const props = defineProps(['title'])
const emit = defineEmits(['update', 'delete'])
function sendUpdate() {
emit('update', { time: new Date(), status: 'updated' })
}
</script>

La directiva v-on (abreviada como @) se usa para escuchar eventos DOM y ejecutar código cuando se disparan.

Ejemplos de v-on/@
<template>
<!-- Eventos básicos del DOM -->
<button v-on:click="contador++">Incrementar</button>
<button @click="incrementar">Incrementar (abreviado)</button>
<!-- Modificadores de eventos -->
<form @submit.prevent="enviarFormulario">...</form>
<input @keyup.enter="buscar">
<div @click.stop="handleClick">No propaga el evento</div>
<button @click.once="oneTimeClick">Solo se ejecuta una vez</button>
<!-- Eventos con argumentos -->
<button @click="eliminar(item.id)">Eliminar</button>
<!-- Acceder al evento original -->
<input @input="updateValue($event)">
<!-- Eventos personalizados de componentes -->
<MiComponente
@update="handleUpdate"
@custom-event="handleCustomEvent"
/>
</template>
<script setup>
import { ref } from 'vue'
import MiComponente from './MiComponente.vue'
const contador = ref(0)
function incrementar() {
contador.value++
}
function enviarFormulario() {
// Lógica para enviar formulario
}
function buscar() {
// Lógica para buscar
}
function eliminar(id) {
console.log('Eliminando item:', id)
}
function updateValue(event) {
console.log('Valor:', event.target.value)
}
function handleUpdate(payload) {
console.log('Actualización:', payload)
}
function handleCustomEvent(data) {
console.log('Evento personalizado:', data)
}
</script>
ModificadorDescripción
.stopEquivalente a event.stopPropagation()
.preventEquivalente a event.preventDefault()
.captureUsa el modo de captura para el evento
.selfSolo dispara si el evento ocurre en este elemento
.onceEl evento se dispara solo una vez
.passiveIndica que el evento no llamará a preventDefault()
🐝