- Propósito de setup en Vue 3
La función setup se introdujo para facilitar el uso de la Composition API en Vue 3, permitiendo organizar la lógica del componente de manera más modular.
- Ventajas sobre la API de Opciones
La API de Opciones (data, computed, methods, watch) puede ser eficaz, pero en componentes grandes dificulta la lectura. Con setup, se puede extraer lógica en funciones reutilizables, ocultando detalles innecesarios.
- Posición en el Ciclo de Vida
setup se ejecuta antes de created y beforeCreated, reemplazándolos. No se puede acceder a this dentro de setup. En su lugar, se usan ganchos del ciclo de vida como:
onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered
- Parámetros de setup
Acepta props y context. props es reactivo y no debe desestructurarse directamente; se usa toRefs. context no es reactivo y puede desestructurarse. Debe retornar un objeto.
props: ['valor']
setup(props, contexto) {
// const {valor} = props // Incorrecto
const {valor} = toRefs(props) // Correcto
const {attrs, slots, emit} = contexto // Correcto
return { valor }
}
- Jerarquía de Prioridades
Si data, props y setup definen la misma propiedad, la de setup tiene mayor prioridad. Ejemplo:
// Componente padre
setup() {
const dato = ref('dato del padre')
return { dato }
}
// Componente hijo
<template>
<div>
<h1>{{ dato }}</h1>
</div>
</template>
<script>
import { toRefs } from 'vue'
export default {
props: ['dato'],
data() {
return { dato: 'dato del hijo (data)' }
},
setup(props) {
let dato = toRefs(props)
dato = 'dato del hijo (setup)'
return { dato }
}
}
</script>
El resultado mostrará "dato del hijo (setup)".
- Importación de Funciones
Para usar ref, toRefs, computed, watch, etc., en setup, se deben importar explícitamente de Vue:
import { toRefs, ref, onMounted, nextTick } from 'vue'
- Aceso a Refs de Componentes Hijo
Para acceder a un componente hijo, se usa ref. Ejemplo con un formulario:
// Plantilla
<a-form ref="miFormulario" :model="datosFormulario" :rules="reglas">
<a-form-item label="Nombre">
<a-input v-model:value="datosFormulario.nombre" />
</a-form-item>
<button @click="enviarFormulario">Enviar</button>
</a-form>
// En setup
setup() {
const miFormulario = ref(null) // Ref para el componente
const datosFormulario = reactive({ nombre: '' })
const reglas = { nombre: [{ required: true, message: 'Requerido' }] }
const enviarFormulario = () => {
miFormulario.value.validate()
.then(() => console.log('Éxito'))
.catch(error => console.log('Error', error))
}
const reiniciarFormulario = () => {
miFormulario.value.resetFields()
}
return { miFormulario, datosFormulario, reglas, enviarFormulario, reiniciarFormulario }
}
- Problema con Formularios en Versiones Específicas
En ant-design-vue ^2.0.0-rc.8 con Vue ^3.0.0, el evento submit puede fallar en la validación. Solución: usar un botón con método en lugar del evento submit del formulario.
// Evitar
<a-form @submit='enviarFormulario'>...</a-form>
// Mejor usar
<button @click="enviarFormulario">Enviar</button>
- Conflictos de Versiones
Hasta enero de 2021, Vite ^1.0.0-rc.1 y ant-design-vue ^2.0.0-rc.8 tenían incompatibilidades; se recomienda usar Vue CLI.
- Invocar Métodos de Componentes Hijo
Pasos: el hijo expone métodos en setup; el padre asigna un ref al hijo; en setup del padre, se accede al hijo via ref.
// Componente hijo
<template>
<div>{{ valorRef }}</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const valorRef = ref('')
const aceptarValor = (nuevoValor) => { valorRef.value = nuevoValor }
return { aceptarValor, valorRef }
}
})
</script>
// Componente padre
<template>
<button @click="enviarDato">Enviar</button>
<Hijo ref="hijoRef" />
</template>
<script>
import { defineComponent, ref } from 'vue'
import Hijo from './Hijo.vue'
export default defineComponent({
components: { Hijo },
setup() {
const hijoRef = ref()
const enviarDato = () => {
hijoRef.value.aceptarValor('nuevo valor')
}
return { hijoRef, enviarDato }
}
})
</script>
- Uso de defineComponent
defineComponent ayuda con la inferencia de tipos en TypeScript. Puede recibir props, setup, etc. Si solo se pasa setup, no se accede a props.
// Método 1: con props
export default defineComponent({
props: ['param'],
setup(props) {
const valor = ref(props.param)
return { valor }
}
})
// Método 2: solo setup (sin acceso a props)
export default defineComponent((props, context) => {
// props no disponible aquí
})
- Uso de watch para Observación Múltiple y Profunda
watch permite observar múltiples fuentes con arrays. Ejemplo:
import { ref, reactive, watch } from 'vue'
const miRef = ref('inicial')
const datos = reactive({
formulario: {
campo: 'valor'
}
})
const desuscribir = watch(
[miRef, () => datos.formulario.campo],
([nuevoRef, nuevoCampo], [viejoRef, viejoCampo]) => {
console.log('Cambios:', nuevoRef, nuevoCampo)
},
{ deep: true }
)
// Para detener la observación
setTimeout(() => desuscribir(), 5000)
- Uso de watchEffect
watchEffect ejecuta automáticamente una función reactivamente, sin proporcionar valores antiguos/nuevos. Ejemplo:
import { ref, watchEffect } from 'vue'
const contador = ref(0)
const detener = watchEffect(() => {
console.log('Contador actual:', contador.value)
})
// Detener después de un tiempo
setTimeout(() => detener(), 10000)
// Incrementar contador
setInterval(() => contador.value++, 1000)
El parámetro flush controla el momento de ejecución: 'pre' (antes de actualización), 'post' (después), 'sync' (sincrónico).
- Acceso a Router y Route
Para usar el router y la ruta en setup, se importan de vue-router:
import { useRoute, useRouter } from 'vue-router'
setup() {
const ruta = useRoute()
const enrutador = useRouter()
// Uso de ruta.query, enrutador.push(), etc.
}