Skip to main content

Основні директиви у Vue.js

Директиви Vue.js - це атрибути з префіксом v-, які роблять DOM-елементи реактивними прямо в шаблоні. Замість того щоб вручну викликати document.querySelector і перемикати класи, ти оголошуєш поведінку, а Vue сам оновлює DOM.

Теорія

TL;DR

  • Директиви - це як стікери на HTML: v-if видаляє елемент коли умова хибна, v-for створює по одному елементу на кожен запис масиву
  • Шість основних: v-if/v-else для умов, v-for для списків, v-bind (:) для атрибутів, v-on (@) для подій, v-model для форм, v-show для перемикання через CSS
  • v-if знищує DOM-вузол і запускає lifecycle hooks; v-show просто ставить display: none
  • До v-for завжди додавай :key зі стабільним унікальним значенням, не індексом масиву

Швидкий приклад

vue
<template> <p v-if="show">Visible!</p> <p v-else>Hidden.</p> <ul> <li v-for="fruit in fruits" :key="fruit">{{ fruit }}</li> </ul> </template> <script setup> import { ref } from 'vue' const show = ref(true) // зміни на false - абзаци поміняються місцями const fruits = ref(['Apple', 'Banana']) </script>

Коли show дорівнює true, перший абзац рендериться, а другого в DOM немає взагалі. Зміни show на false і Vue поміняє їх місцями. Список відображає по одному <li> на елемент, а :key="fruit" дозволяє Vue відстежувати кожен окремо.

Шість директив

v-if / v-else / v-else-if додають або видаляють елементи залежно від умови. Коли v-if стає хибним, Vue викликає unmounted на всіх компонентах всередині та видаляє вузол. Коли умова знову стає true, Vue створює свіжий вузол і запускає mounted.

vue
<p v-if="isLoggedIn">Welcome!</p> <p v-else>Please log in.</p>

v-show керує видимістю через display: none. Вузол залишається в DOM, lifecycle hooks не спрацьовують. Тому v-show дешевший для елементів, які часто перемикаються.

v-for рендерить список, ітеруючись по масивах або об'єктах. Атрибут :key не опціональний. Без нього Vue перевикористовує DOM-вузли за позицією, і будь-яке переупорядкування дає неправильний стан, збиті поля вводу та перемішаний стан компонентів.

vue
<li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>

v-bind (скорочено :) прив'язує JavaScript-значення до HTML-атрибута. :src="imageUrl" ідентично v-bind:src="imageUrl". Працює з будь-яким атрибутом: class, style, disabled, href, кастомними data-атрибутами.

v-on (скорочено @) додає обробники подій. @click="handler" замінює addEventListener('click', handler) на цьому елементі. Вбудовані вирази теж працюють: @click="count++".

v-model забезпечує двосторонню прив'язку (two-way binding) для полів форм. Під капотом це :value="text" @input="text = $event.target.value". Для кастомних компонентів у Vue 3 директива прив'язується до пропу modelValue і слухає подію update:modelValue.

v-if vs v-show: коли що обирати

v-ifv-show
DOM-вузолВидаляється і відтворюєтьсяЗавжди присутній
display: noneНіТак, коли прихований
Lifecycle hooksТакНі
Підходить дляУмов, що рідко змінюютьсяЧастого показу та приховування

Одного разу я бачив команду, яка використовувала v-if для спінера завантаження, що перемикався 10 разів на секунду в polling-циклі. Кожен тогл був повним знищенням і відтворенням DOM. Перехід на v-show скоротив витрати до однієї операції зі стилем.

Як директиви працюють всередині

Компілятор шаблонів Vue читає атрибути v- під час збірки і перетворює їх на функції рендерингу (render functions). Ці функції повертають VNode - звичайні JavaScript-об'єкти, що описують DOM. Під час виконання Vue загортає значення ref() в ES6 Proxy. Коли функція рендерингу зчитує реактивне значення, Proxy записує залежність. При зміні значення Vue ставить рендер у чергу, порівнює нове дерево VNode зі старим і патчить лише змінені вузли.

Оновлення батчуються на тік мікрозадачі. Тобто якщо ти зміниш три ref-и в одній функції, Vue виконає один DOM-патч, а не три.

Типові помилки

Індекс масиву як :key у v-for

vue
<!-- Неправильно: індекс не працює при переупорядкуванні --> <li v-for="(item, index) in items" :key="index">{{ item.name }}</li> <!-- Правильно: стабільний унікальний id --> <li v-for="item in items" :key="item.id">{{ item.name }}</li>

Коли елементи переупорядковуються, ключі-індекси змушують Vue зіставляти DOM-вузли з неправильними даними. Поля вводу втрачають фокус, анімації відтворюються на неправильному елементі, стан компонентів перемішується.

Очікування v-else після v-for

vue
<!-- Неправильно: Vue видасть помилку --> <li v-for="item in items">{{ item }}</li> <p v-else>Немає елементів.</p> <!-- Правильно: обгорни в контейнер з v-if --> <template v-if="items.length"> <li v-for="item in items" :key="item.id">{{ item }}</li> </template> <p v-else>Немає елементів.</p>

v-else повинен бути прямим сусідом одиночного v-if-елемента, а не v-for.

v-if і v-for на одному елементі

vue
<!-- Уникай: у Vue 3 v-if виконується першим і не має доступу до змінних v-for --> <li v-for="item in items" v-if="item.active" :key="item.id">{{ item.name }}</li> <!-- Краще: фільтруй через computed --> <li v-for="item in activeItems" :key="item.id">{{ item.name }}</li>

Відфільтруй масив через computed ref і ітеруйся по результату.

Де застосовується

  • Nuxt.js: v-if="status === 'pending'" показує спінер під час async-завантаження сторінок
  • Vuetify: v-for="item in menuItems" :key="item.title" на <v-list-item> в навігаційних drawer-ах
  • Quasar: v-model на <q-input> для двосторонньої прив'язки в PWA-формах
  • Pinia: v-for="todo in store.todos" тягне дані зі стору безпосередньо в шаблон

Питання на співбесіді

Q: Яка різниця між v-if і v-show?
A: v-if видаляє вузол з DOM коли умова хибна і відтворює його коли стає true. v-show залишає вузол в DOM і перемикає display: none. Для частих переключень v-show дешевший. Для контенту, який з'являється рідко, v-if краще - вузол взагалі не існує коли прихований.

Q: Чому :key важливий у v-for і чому індекс - поганий ключ?
A: Ключі дозволяють Vue зіставляти DOM-вузли з конкретними записами даних. Без них Vue матчить за позицією. Індекс погоний з тієї ж причини: якщо видалити або переупорядкувати елементи, індекси зсуваються і Vue зіставляє неправильні вузли.

Q: Чи можна поставити v-if і v-for на один елемент?
A: Можна, але краще уникати. У Vue 3 v-if обчислюється першим і не має доступу до змінних v-for. Відфільтруй масив через computed ref і ітеруйся по ньому.

Q: Як v-model працює під капотом?
A: Це скорочення для :value + @input на нативних полях вводу. Для кастомних компонентів у Vue 3 прив'язується до пропу modelValue і слухає подію update:modelValue.

Q: (Senior) Як Vue знає яку директиву оновлювати при зміні реактивного значення?
A: Vue загортає реактивні дані в ES6 Proxy. Коли директива зчитує реактивне значення під час рендерингу, Proxy записує залежність. При зміні значення Vue позначає відповідні вотчери як застарілі і ставить рендер у чергу. Реальний DOM-патч відбувається на наступному тіку мікрозадачі - всі синхронні мутації збираються в один прохід.

Приклади

Базовий: перемикання стану авторизації

vue
<template> <button @click="isLoggedIn = !isLoggedIn"> {{ isLoggedIn ? 'Logout' : 'Login' }} </button> <p v-if="isLoggedIn">Welcome, user!</p> <p v-else>Please sign in first.</p> </template> <script setup> import { ref } from 'vue' const isLoggedIn = ref(false) </script>

Текст кнопки оновлюється реактивно. Абзаци міняються місцями на кожному кліку. Компонент управляє одним булевим значенням, Vue обробляє всі DOM-зміни.

Середній: список задач з API

vue
<template> <ul> <li v-for="todo in todos" :key="todo.id" @click="todo.done = !todo.done" > {{ todo.text }} <span v-if="todo.done">✓</span> </li> </ul> </template> <script setup> import { ref, onMounted } from 'vue' const todos = ref([]) onMounted(async () => { const res = await fetch('/api/todos') todos.value = await res.json() }) </script>

Після монтування задачі завантажуються з API і v-for їх рендерить. Клік по рядку перемикає done. Vue оновлює тільки <span> для конкретного елемента, не перерендерюючи весь список.

Просунутий: вкладені v-for зі стабільними ключами

vue
<template> <div v-for="user in users" :key="user.id"> <strong>{{ user.name }}</strong> <ul> <li v-for="task in user.tasks" :key="task.id">{{ task.text }}</li> </ul> </div> </template> <script setup> import { ref } from 'vue' const users = ref([ { id: 1, name: 'Alice', tasks: [{ id: 1, text: 'Design review' }, { id: 2, text: 'Write tests' }] }, { id: 2, name: 'Bob', tasks: [{ id: 1, text: 'Deploy staging' }] } ]) </script>

Обидва цикли використовують id як ключ. Якщо до списку задач Alice додається новий елемент, Vue вставляє рівно один <li>, не торкаючись рядка Bob. Без task.id як ключа будь-яка мутація масиву викликає повне переузгодження списку за позицією.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?