Основні директиви у 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зі стабільним унікальним значенням, не індексом масиву
Швидкий приклад
<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.
<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-вузли за позицією, і будь-яке переупорядкування дає неправильний стан, збиті поля вводу та перемішаний стан компонентів.
<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-if | v-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
<!-- Неправильно: індекс не працює при переупорядкуванні -->
<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 видасть помилку -->
<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 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-патч відбувається на наступному тіку мікрозадачі - всі синхронні мутації збираються в один прохід.
Приклади
Базовий: перемикання стану авторизації
<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
<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 зі стабільними ключами
<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 як ключа будь-яка мутація масиву викликає повне переузгодження списку за позицією.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.