Vue поощряет декларативный подход — вы описываете состояние, и Vue сам обновляет DOM. Но иногда нужно обратиться к DOM-элементу напрямую:
Для таких случаев Vue предоставляет **template refs**.
В шаблоне добавьте атрибут ref с именем:
<template>
<input ref="inputEl" type="text" placeholder="Введите текст">
<button @click="focusInput">Поставить фокус</button>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputEl = ref(null) // имя переменной совпадает с ref="inputEl"
onMounted(() => {
// DOM доступен только после монтирования компонента
inputEl.value.focus()
})
function focusInput() {
inputEl.value.focus()
}
</script>Vue 3.5 добавил более явный способ через useTemplateRef():
<template>
<input ref="myInput">
</template>
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const inputEl = useTemplateRef('myInput')
onMounted(() => {
console.log(inputEl.value) // HTMLInputElement
})
</script>Если ref указан на дочернем компоненте, вы получаете доступ к его публичному интерфейсу. В Composition API с <script setup> компонент по умолчанию закрыт — нужно явно использовать defineExpose:
<!-- ChildComponent.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
// Явно экспортируем то, что хотим сделать доступным
defineExpose({ count, increment: () => count.value++ })
</script><!-- ParentComponent.vue -->
<template>
<ChildComponent ref="childRef" />
<button @click="childRef.increment()">Увеличить в дочернем</button>
</template>
<script setup>
import { ref } from 'vue'
const childRef = ref(null)
</script>Если ref используется в v-for, переменная получит массив DOM-элементов:
<template>
<li v-for="item in items" :key="item.id" ref="listItems">
{{ item.name }}
</li>
</template>
<script setup>
const listItems = ref([]) // будет массивом li-элементов
</script>Используй refs когда:
Не используй refs для:
Помни: ref.value равен null до монтирования компонента. Обращайся к нему в onMounted или позже.
Эмуляция template refs: регистрация DOM-элементов по имени и доступ к ним после монтирования
// Эмулируем систему template refs Vue — регистрацию и доступ к DOM-элементам.
class ComponentInstance {
constructor(name) {
this.name = name
this._refs = {}
this._mounted = false
this._mountCallbacks = []
}
// Аналог ref="name" в шаблоне — регистрирует элемент
registerRef(name, element) {
this._refs[name] = element
console.log(` [ref] "${name}" зарегистрирован: <${element.tagName}>`)
}
// Аналог onMounted — вызывается после рендера
onMounted(callback) {
if (this._mounted) {
callback()
} else {
this._mountCallbacks.push(callback)
}
}
// Симуляция процесса монтирования
mount() {
console.log(`\nМонтирование компонента "${this.name}"...`)
this._mounted = true
this._mountCallbacks.forEach(cb => cb())
this._mountCallbacks = []
}
// Получить ref по имени (как inputEl.value в Vue)
getRef(name) {
if (!this._mounted) {
console.warn(` ПРЕДУПРЕЖДЕНИЕ: Обращение к ref "${name}" до монтирования — null!`)
return null
}
return this._refs[name] || null
}
}
// Псевдо-DOM элементы
const pseudoInput = {
tagName: 'INPUT',
value: '',
focused: false,
focus() {
this.focused = true
console.log(' [DOM] input.focus() вызван — поле в фокусе')
}
}
const pseudoVideo = {
tagName: 'VIDEO',
playing: false,
play() {
this.playing = true
console.log(' [DOM] video.play() вызван — видео запущено')
},
pause() {
this.playing = false
console.log(' [DOM] video.pause() вызван — видео остановлено')
}
}
// Создаём компонент
const comp = new ComponentInstance('FormComponent')
// Попытка обратиться к ref ДО монтирования
console.log('=== До монтирования ===')
comp.getRef('inputEl') // вернёт null с предупреждением
// Регистрируем рефы (как при рендере шаблона)
comp.registerRef('inputEl', pseudoInput)
comp.registerRef('videoEl', pseudoVideo)
// Регистрируем колбэк onMounted
comp.onMounted(() => {
console.log('\n [onMounted] Компонент смонтирован! Ставим фокус...')
const input = comp.getRef('inputEl')
if (input) input.focus()
})
// Монтируем
comp.mount()
// Теперь можно работать с рефами
console.log('\n=== Работа с рефами после монтирования ===')
const video = comp.getRef('videoEl')
video.play()
console.log(' Видео играет:', video.playing)
video.pause()
console.log(' Видео играет:', video.playing)
console.log('\n=== Итог ===')
console.log('inputEl.focused:', comp.getRef('inputEl').focused)
console.log('videoEl.playing:', comp.getRef('videoEl').playing)Vue поощряет декларативный подход — вы описываете состояние, и Vue сам обновляет DOM. Но иногда нужно обратиться к DOM-элементу напрямую:
Для таких случаев Vue предоставляет **template refs**.
В шаблоне добавьте атрибут ref с именем:
<template>
<input ref="inputEl" type="text" placeholder="Введите текст">
<button @click="focusInput">Поставить фокус</button>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputEl = ref(null) // имя переменной совпадает с ref="inputEl"
onMounted(() => {
// DOM доступен только после монтирования компонента
inputEl.value.focus()
})
function focusInput() {
inputEl.value.focus()
}
</script>Vue 3.5 добавил более явный способ через useTemplateRef():
<template>
<input ref="myInput">
</template>
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const inputEl = useTemplateRef('myInput')
onMounted(() => {
console.log(inputEl.value) // HTMLInputElement
})
</script>Если ref указан на дочернем компоненте, вы получаете доступ к его публичному интерфейсу. В Composition API с <script setup> компонент по умолчанию закрыт — нужно явно использовать defineExpose:
<!-- ChildComponent.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
// Явно экспортируем то, что хотим сделать доступным
defineExpose({ count, increment: () => count.value++ })
</script><!-- ParentComponent.vue -->
<template>
<ChildComponent ref="childRef" />
<button @click="childRef.increment()">Увеличить в дочернем</button>
</template>
<script setup>
import { ref } from 'vue'
const childRef = ref(null)
</script>Если ref используется в v-for, переменная получит массив DOM-элементов:
<template>
<li v-for="item in items" :key="item.id" ref="listItems">
{{ item.name }}
</li>
</template>
<script setup>
const listItems = ref([]) // будет массивом li-элементов
</script>Используй refs когда:
Не используй refs для:
Помни: ref.value равен null до монтирования компонента. Обращайся к нему в onMounted или позже.
Эмуляция template refs: регистрация DOM-элементов по имени и доступ к ним после монтирования
// Эмулируем систему template refs Vue — регистрацию и доступ к DOM-элементам.
class ComponentInstance {
constructor(name) {
this.name = name
this._refs = {}
this._mounted = false
this._mountCallbacks = []
}
// Аналог ref="name" в шаблоне — регистрирует элемент
registerRef(name, element) {
this._refs[name] = element
console.log(` [ref] "${name}" зарегистрирован: <${element.tagName}>`)
}
// Аналог onMounted — вызывается после рендера
onMounted(callback) {
if (this._mounted) {
callback()
} else {
this._mountCallbacks.push(callback)
}
}
// Симуляция процесса монтирования
mount() {
console.log(`\nМонтирование компонента "${this.name}"...`)
this._mounted = true
this._mountCallbacks.forEach(cb => cb())
this._mountCallbacks = []
}
// Получить ref по имени (как inputEl.value в Vue)
getRef(name) {
if (!this._mounted) {
console.warn(` ПРЕДУПРЕЖДЕНИЕ: Обращение к ref "${name}" до монтирования — null!`)
return null
}
return this._refs[name] || null
}
}
// Псевдо-DOM элементы
const pseudoInput = {
tagName: 'INPUT',
value: '',
focused: false,
focus() {
this.focused = true
console.log(' [DOM] input.focus() вызван — поле в фокусе')
}
}
const pseudoVideo = {
tagName: 'VIDEO',
playing: false,
play() {
this.playing = true
console.log(' [DOM] video.play() вызван — видео запущено')
},
pause() {
this.playing = false
console.log(' [DOM] video.pause() вызван — видео остановлено')
}
}
// Создаём компонент
const comp = new ComponentInstance('FormComponent')
// Попытка обратиться к ref ДО монтирования
console.log('=== До монтирования ===')
comp.getRef('inputEl') // вернёт null с предупреждением
// Регистрируем рефы (как при рендере шаблона)
comp.registerRef('inputEl', pseudoInput)
comp.registerRef('videoEl', pseudoVideo)
// Регистрируем колбэк onMounted
comp.onMounted(() => {
console.log('\n [onMounted] Компонент смонтирован! Ставим фокус...')
const input = comp.getRef('inputEl')
if (input) input.focus()
})
// Монтируем
comp.mount()
// Теперь можно работать с рефами
console.log('\n=== Работа с рефами после монтирования ===')
const video = comp.getRef('videoEl')
video.play()
console.log(' Видео играет:', video.playing)
video.pause()
console.log(' Видео играет:', video.playing)
console.log('\n=== Итог ===')
console.log('inputEl.focused:', comp.getRef('inputEl').focused)
console.log('videoEl.playing:', comp.getRef('videoEl').playing)Реализуй класс `TemplateRefRegistry`, который хранит именованные ссылки на объекты. Методы: `register(name, element)` — регистрирует элемент; `get(name)` — возвращает элемент или `null` если не смонтирован; `mounted()` — отмечает компонент как смонтированный; `isMounted()` — возвращает булево значение. До вызова `mounted()` метод `get()` должен возвращать `null`.
В конструкторе создай `this._refs = {}` и `this._mounted = false`. В `get()` проверяй `this._mounted` и возвращай `this._refs[name] ?? null`.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке