Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Шаблонні посилання у Vue.js». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Template refs** у Vue.js дають прямий доступ до DOM-елементів або екземплярів дочірніх компонентів. До монтування ref дорівнює `null`, після `onMounted` - готовий до роботи. ```vue const inputRef = ref(null) onMounted(() => { inputRef.value?.focus() }) // <input ref="inputRef" /> ``` **Ключове:** refs для фокусу, скролу, canvas або сторонніх бібліотек, а не замість реактивних даних.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Template refs** у Vue.js дають прямий доступ до DOM-елементів або екземплярів дочірніх компонентів з JavaScript. ## Теорія ### TL;DR - Template ref - це змінна `ref()`, яку Vue заповнює справжнім DOM-вузлом (або екземпляром компонента) після монтування - До монтування значення завжди `null` - У `<script setup>` дочірні компоненти нічого не відкривають за замовчуванням - потрібен `defineExpose`, щоб надати доступ батьківському компоненту - Refs потрібні там, де реактивність Vue не справляється самостійно: фокус, скрол, canvas, ініціалізація сторонніх бібліотек - Реактивні дані вирішують більшість задач. Refs - для виключень. ### Швидкий приклад ```vue <script setup> import { ref, onMounted } from 'vue' const inputRef = ref(null) onMounted(() => { // до монтування null, після - HTMLInputElement inputRef.value?.focus() }) </script> <template> <input ref="inputRef" type="text" /> </template> ``` Рядок `"inputRef"` у шаблоні відповідає назві змінної в `<script setup>`. Vue з'єднує їх автоматично. ### Коли використовувати template refs Refs доречні тоді, коли реактивна модель Vue не вирішує задачу самостійно: - Сфокусувати поле після монтування або після дії користувача - Прокрутити до конкретного елемента (`scrollIntoView`) - Прочитати розміри елемента (`getBoundingClientRect`, `offsetHeight`) - Малювати на canvas (`getContext('2d')`) - Ініціалізувати сторонню бібліотеку (Chart.js, D3), якій потрібен справжній DOM-вузол Якщо задача вирішується через `v-model`, `:class` або обчислювані дані - краще йти тим шляхом. ### Refs у компонентах і defineExpose Якщо поставити `ref` на дочірній компонент, отримаєш доступ до його публічного екземпляра. Але компоненти з `<script setup>` не відкривають нічого за замовчуванням. Дочірній компонент повинен явно оголосити, що доступне ззовні: ```vue <!-- ChildComponent.vue --> <script setup> import { ref } from 'vue' const count = ref(0) function reset() { count.value = 0 } defineExpose({ count, reset }) </script> ``` ```vue <!-- Parent.vue --> <script setup> import { ref } from 'vue' import ChildComponent from './ChildComponent.vue' const childRef = ref(null) function handleReset() { childRef.value?.reset() } </script> <template> <ChildComponent ref="childRef" /> <button @click="handleReset">Скинути</button> </template> ``` Без `defineExpose` - `childRef.value` буде порожнім proxy. Будь-який виклик методу просто нічого не зробить. ### Refs всередині v-for Якщо `ref` стоїть на елементі всередині `v-for`, змінна ref стає масивом: ```vue <script setup> import { ref, onMounted } from 'vue' const listRefs = ref([]) onMounted(() => { listRefs.value[0]?.scrollIntoView() }) </script> <template> <li v-for="item in items" :key="item.id" ref="listRefs"> {{ item.name }} </li> </template> ``` Порядок у масиві відповідає порядку в DOM, а не порядку додавання елементів. ### Часті помилки **Звернення до ref.value до монтування.** Ref має значення `null` поза `onMounted` або пізнішими хуками. Більшість таких помилок - це виклик `.focus()` прямо в тілі компонента. ```js // Неправильно - ref тут null const inputRef = ref(null) inputRef.value.focus() // TypeError: Cannot read properties of null // Правильно onMounted(() => { inputRef.value?.focus() }) ``` **v-if скидає ref.** Коли умова `v-if` стає `false`, елемент видаляється з DOM і ref повертається до `null`. При наступному рендері починається новий цикл монтування. **Відсутній defineExpose у дочірньому компоненті.** Ставиш ref на компонент, намагаєшся викликати метод - і нічого не відбувається. Потрібен `defineExpose({ methodName })` у дочірньому компоненті. **Ручне читання стану DOM замість реактивності.** Брати `inputRef.value.value` для читання тексту поля і оновлювати його вручну - для цього є `v-model`. ### Питання для співбесіди **Q:** Чому template ref дорівнює `null` до монтування? **A:** Vue спочатку будує дерево virtual DOM, а потім застосовує його до реального DOM під час фази монтування. До цього моменту реального елемента ще не існує. **Q:** Яка різниця між `ref="inputRef"` і `:ref="fn"`? **A:** Рядкова версія прив'язується до змінної `inputRef` у `<script setup>`. Прив'язана версія приймає функцію, яка отримує елемент напряму - корисно для зберігання кількох refs у Map. **Q:** Чи можна використовувати template refs з Options API? **A:** Так. В Options API доступ до refs через `this.$refs.inputRef`. Поведінка та сама - `null` до монтування, заповнюється після. **Q:** Що відбувається з ref всередині `v-if`, коли умова змінюється? **A:** Коли умова стає `false`, Vue демонтує елемент і встановлює ref в `null`. Коли умова повертається до `true`, Vue монтує новий елемент і знову заповнює ref. ## Приклади ### Фокус на полі форми після монтування Форма входу, яка фокусує поле email відразу після монтування. ```vue <script setup> import { ref, onMounted } from 'vue' const emailRef = ref(null) onMounted(() => { emailRef.value?.focus() }) </script> <template> <form> <input ref="emailRef" type="email" placeholder="Email" /> <input type="password" placeholder="Пароль" /> <button type="submit">Увійти</button> </form> </template> ``` Опціональний ланцюжок `?.` захищає від рідкісного випадку, коли компонент демонтується до спрацювання `onMounted`. ### Ініціалізація графіка через canvas ref Сторонні бібліотеки на кшталт Chart.js потребують справжнього DOM-вузла. Передавати реактивну змінну тут не вийде. ```vue <script setup> import { ref, onMounted, onUnmounted } from 'vue' import Chart from 'chart.js/auto' const canvasRef = ref(null) let chart = null onMounted(() => { if (!canvasRef.value) return chart = new Chart(canvasRef.value, { type: 'bar', data: { labels: ['Січ', 'Лют', 'Бер'], datasets: [{ label: 'Продажі', data: [12, 19, 8] }] } }) }) onUnmounted(() => { chart?.destroy() // щоб уникнути витоків пам'яті }) </script> <template> <canvas ref="canvasRef" width="400" height="200" /> </template> ``` Команди часто намагаються підключити Chart.js через реактивний стан. Так не виходить. Canvas потребує справжнього вузла, і template ref - єдиний чистий спосіб його отримати.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.