Los template refs comienzan como null y se convierten en el elemento DOM o en la instancia del componente después del montaje. En TypeScript, debes tener en cuenta esto con un tipo unión y proteger el acceso con optional chaining o hooks del ciclo de vida.
Refs de elementos DOM
<script setup lang="ts">
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputRef.value?.focus()
})
</script>
<template>
<input ref="inputRef" />
</template>El tipo es HTMLInputElement | null porque el ref es null durante el setup y se convierte en el elemento solo después de que el componente se monta.
useTemplateRef (Vue 3.5+)
useTemplateRef separa el nombre del template ref de la variable y mejora la inferencia de tipos:
<script setup lang="ts">
const input = useTemplateRef<HTMLInputElement>('my-input')
onMounted(() => {
input.value?.focus()
})
</script>
<template>
<input ref="my-input" />
</template>La cadena 'my-input' coincide con el atributo ref en el template. El nombre de la variable input es independiente.
Refs de componentes
Usa InstanceType<typeof Component> para tipar un ref a un componente hijo:
<script setup lang="ts">
import ChildForm from './ChildForm.vue'
const formRef = ref<InstanceType<typeof ChildForm> | null>(null)
function submit() {
formRef.value?.validate()
}
</script>
<template>
<ChildForm ref="formRef" />
</template>El hijo debe exponer el método con defineExpose:
<!-- ChildForm.vue -->
<script setup lang="ts">
function validate() {
// lógica de validación
return isValid.value
}
defineExpose({ validate })
</script>Sin defineExpose, el padre no puede acceder a ningún estado interno ni a los métodos del hijo.
Refs con v-if
Cuando un elemento está detrás de v-if, el ref vuelve a ser null cuando la condición es false:
<script setup lang="ts">
const showModal = ref(false)
const modalRef = ref<HTMLDivElement | null>(null)
watch(modalRef, (el) => {
if (el) {
el.focus() // el elemento acaba de montarse
}
})
</script>
<template>
<div v-if="showModal" ref="modalRef" tabindex="-1">
Contenido del modal
</div>
</template>Usa siempre optional chaining o comprobaciones de null cuando el target del ref puede renderizarse de forma condicional.
Refs con v-for
Con v-for, el ref se convierte en un array. Usa un ref de función para poblarlo:
<script setup lang="ts">
const items = ref(['a', 'b', 'c'])
const itemRefs = ref<(HTMLLIElement | null)[]>([])
onMounted(() => {
itemRefs.value[0]?.focus()
})
</script>
<template>
<ul>
<li
v-for="(item, index) in items"
:key="item"
:ref="(el) => { itemRefs[index] = el as HTMLLIElement }"
>
{{ item }}
</li>
</ul>
</template>Tras operaciones asíncronas
Si haces await dentro de onMounted, el componente podría haberse desmontado cuando la promesa se resuelva:
onMounted(async () => {
await fetchData()
// el componente podría no existir ya, comprueba antes de acceder
if (inputRef.value) {
inputRef.value.scrollTop = 0
}
})Referencia rápida
| Escenario | Tipo |
|---|---|
| Elemento DOM | ref<HTMLDivElement | null>(null) |
| Elemento input | ref<HTMLInputElement | null>(null) |
| Elemento canvas | ref<HTMLCanvasElement | null>(null) |
| Componente hijo | ref<InstanceType<typeof MyComponent> | null>(null) |
| Array desde v-for | ref<(HTMLLIElement | null)[]>([]) |
| useTemplateRef (3.5+) | useTemplateRef<HTMLInputElement>('nombre') |