Skip to content

Template Refs в Vue 3

1. Что такое Template Refs

Template Refs (шаблонные ссылки) - это механизм во Vue 3, который позволяет получить прямой доступ к DOM-элементам или дочерним компонентам, объявленным в шаблоне. Это полезно, когда необходимо взаимодействовать с DOM-элементами напрямую, например, для фокусировки полей ввода, анимаций или интеграции со сторонними библиотеками.

2. Использование в Composition API

Базовое использование

vue
<script setup>
import { ref, onMounted } from 'vue';

// Создание ref для хранения ссылки на DOM-элемент
const inputRef = ref(null);

// После монтирования компонента можно получить доступ к DOM-элементу
onMounted(() => {
  // Прямой доступ к DOM-элементу
  inputRef.value.focus();
});
</script>

<template>
  <!-- Атрибут ref связывает DOM-элемент с переменной inputRef -->
  <input ref="inputRef" type="text" placeholder="Это поле получит фокус автоматически" />
</template>

Доступ к нескольким элементам

vue
<script setup>
import { ref, onMounted } from 'vue';

// Для массива элементов используем массив или объект
const itemRefs = ref([]);

onMounted(() => {
  // Теперь у нас есть массив DOM-элементов
  console.log(itemRefs.value.length);
  
  // Можно работать с каждым элементом
  itemRefs.value.forEach((item, index) => {
    console.log(`Элемент ${index}:`, item);
  });
});
</script>

<template>
  <ul>
    <li v-for="(item, index) in ['Первый', 'Второй', 'Третий']" :key="index"
        ref="itemRefs">
      {{ item }} элемент
    </li>
  </ul>
</template>

3. Refs для компонентов

Доступ к методам и свойствам дочернего компонента

vue
<!-- ParentComponent.vue -->
<script setup>
import { ref, onMounted } from 'vue';
import ChildComponent from './ChildComponent.vue';

const childRef = ref(null);

onMounted(() => {
  // Доступ к методам дочернего компонента
  childRef.value.reset();
  
  // Доступ к публичным свойствам экземпляра
  console.log(childRef.value.publicData);
});

function activateChild() {
  childRef.value.activate();
}
</script>

<template>
  <button @click="activateChild">Активировать дочерний компонент</button>
  <ChildComponent ref="childRef" />
</template>
vue
<!-- ChildComponent.vue -->
<script setup>
import { ref } from 'vue';

const isActive = ref(false);
const publicData = ref('Данные из дочернего компонента');

// Определение методов, которые будут доступны через ref
function reset() {
  isActive.value = false;
}

function activate() {
  isActive.value = true;
}

// Экспорт публичных методов и свойств
defineExpose({
  reset,
  activate,
  publicData
});
</script>

<template>
  <div :class="{ active: isActive }">
    Дочерний компонент
  </div>
</template>

4. Typescript с Template Refs

Типизация DOM-элементов

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue';

// Типизация HTML-элемента
const inputRef = ref<HTMLInputElement | null>(null);

onMounted(() => {
  if (inputRef.value) {
    // TypeScript понимает, что это HTMLInputElement
    inputRef.value.focus();
    
    // Доступ к специфическим свойствам элемента input
    console.log(inputRef.value.value);
    console.log(inputRef.value.type);
  }
});
</script>

<template>
  <input ref="inputRef" type="text" />
</template>

Типизация компонентов

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import CustomForm from './CustomForm.vue';

// Импорт типа для компонента (определенного в самом компоненте)
import type { CustomFormExpose } from './types';

// Типизация ссылки на компонент
const formRef = ref<CustomFormExpose | null>(null);

function submitForm() {
  if (formRef.value) {
    // TypeScript знает о методах, определенных в CustomFormExpose
    const isValid = formRef.value.validate();
    
    if (isValid) {
      formRef.value.submit();
    }
  }
}
</script>

<template>
  <CustomForm ref="formRef" />
  <button @click="submitForm">Отправить форму</button>
</template>

5. Работа с ref в callback функциях

vue
<script setup>
import { ref, onMounted } from 'vue';

const containerRef = ref(null);

function setupObserver() {
  if (!containerRef.value) return;

  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          console.log('Элемент виден в области просмотра');
        }
      });
    },
    { threshold: 0.5 }
  );

  observer.observe(containerRef.value);

  // Очистка наблюдателя при размонтировании компонента
  return () => observer.disconnect();
}

onMounted(setupObserver);
</script>

<template>
  <div ref="containerRef" class="container">
    <p>Этот контейнер отслеживается IntersectionObserver</p>
  </div>
</template>

6. Интеграция со сторонними библиотеками

vue
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';

const chartRef = ref(null);
let chartInstance = null;

onMounted(() => {
  if (!chartRef.value) return;

  // Предположим, что мы используем Chart.js
  chartInstance = new Chart(chartRef.value.getContext('2d'), {
    type: 'bar',
    data: {
      labels: ['Январь', 'Февраль', 'Март'],
      datasets: [{
        label: 'Продажи',
        data: [12, 19, 3],
        backgroundColor: [
          'rgba(255, 99, 132, 0.2)',
          'rgba(54, 162, 235, 0.2)',
          'rgba(255, 206, 86, 0.2)'
        ],
        borderColor: [
          'rgba(255, 99, 132, 1)',
          'rgba(54, 162, 235, 1)',
          'rgba(255, 206, 86, 1)'
        ],
        borderWidth: 1
      }]
    }
  });
});

onBeforeUnmount(() => {
  // Очистка ресурсов при уничтожении компонента
  if (chartInstance) {
    chartInstance.destroy();
    chartInstance = null;
  }
});
</script>

<template>
  <canvas ref="chartRef" width="400" height="200"></canvas>
</template>

7. Функциональный подход с шаблонными ссылками

vue
<script setup>
import { ref, onMounted, watchEffect } from 'vue';

const inputRef = ref(null);
const counter = ref(0);

// watchEffect для автоматического отслеживания зависимостей
watchEffect(() => {
  // Это будет выполняться при каждом изменении counter
  // или при первоначальной установке inputRef
  if (inputRef.value) {
    inputRef.value.dataset.counter = counter.value;
  }
}, { flush: 'post' }); // 'post' гарантирует, что DOM уже обновлен

function increment() {
  counter.value++;
}
</script>

<template>
  <input ref="inputRef" type="text" />
  <button @click="increment">Увеличить счетчик ({{ counter }})</button>
</template>

Template Refs предоставляют мощный механизм для взаимодействия с DOM-элементами, сохраняя при этом основные преимущества реактивной модели Vue. Используйте их, когда вам необходимо выйти за рамки декларативного подхода и получить прямой доступ к элементам.