Директивы: v-if, v-show, v-for
Что такое директивы
Директивы — специальные атрибуты с префиксом v-. Меняют поведение элемента.
v-if — условный рендеринг
Добавляет/удаляет элемент из DOM:
<p v-if="isVisible">Видимый текст</p>
<p v-else-if="isLoading">Загрузка...</p>
<p v-else>Скрытый текст</p>
Когда isVisible = false — элемент удаляется из DOM.
v-show — скрытие через CSS
<p v-show="isVisible">Видимый текст</p>
Когда false — элемент остаётся в DOM, но получает display: none.
v-if vs v-show
| v-if | v-show | |
|---|---|---|
| DOM | Удаляется | Остаётся |
| Переключение | Дорого | Дешево |
| Начальный рендер | Дешево, если false | Дорого |
| Когда использовать | Редко меняется | Часто меняется |
Правило:
- v-if — редко переключается
- v-show — часто переключается (табы, модалки)
Условия
<div v-if="count > 10">Больше 10</div>
<div v-if="user && user.isAdmin">Админ</div>
<div v-if="items.length > 0">Есть элементы</div>
<div v-if="status === 'active'">Активен</div>
Группировка через template
<template v-if="isLoggedIn">
<h1>Добро пожаловать, {{ user.name }}</h1>
<button @click="logout">Выйти</button>
</template>
<template> не создаёт элемент в DOM — просто группирует.
v-for — циклы
По массиву:
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
С индексом:
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
По объекту:
<li v-for="(value, key, index) in object" :key="key">
{{ index }}. {{ key }}: {{ value }}
</li>
По числу:
<span v-for="n in 5" :key="n">{{ n }}</span>
Ключ — key
Обязателен для v-for:
<li v-for="item in items" :key="item.id">
Зачем: Vue понимает, какие элементы изменились, добавлены, удалены. Без key — баги и тормоза.
Правила key:
- Уникальный (обычно
id) - Не индекс массива (если массив меняется)
v-for + v-if
⚠️ Не используй на одном элементе:
<li v-for="item in items" v-if="item.active"> <!-- Плохо -->
Почему: v-if имеет больший приоритет чем v-for. Сначала проверяется условие, потом цикл.
Правильно:
1. Фильтровать заранее:
<script setup>
import { computed } from 'vue';
const activeItems = computed(() => items.value.filter(i => i.active));
</script>
<template>
<li v-for="item in activeItems" :key="item.id">
</template>
2. Через template:
<template v-for="item in items" :key="item.id">
<li v-if="item.active">{{ item.name }}</li>
</template>
Обновление массива
Реактивные методы:
items.value.push(newItem); // ✅
items.value.pop(); // ✅
items.value.shift(); // ✅
items.value.unshift(item); // ✅
items.value.splice(1, 1); // ✅
items.value.sort(); // ✅
items.value.reverse(); // ✅
По индексу:
items.value[0] = newItem; // ✅ в Vue 3 работает
Полный пример
<template>
<div>
<input v-model="search" placeholder="Поиск...">
<p v-if="filtered.length === 0">Ничего не найдено</p>
<ul v-else>
<li v-for="(product, index) in filtered" :key="product.id">
<span>{{ index + 1 }}.</span>
<span>{{ product.name }}</span>
<span v-if="product.sale" class="sale">Скидка!</span>
</li>
</ul>
<button @click="showAll = !showAll">
{{ showAll ? 'Скрыть' : 'Показать всё' }}
</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const search = ref('');
const showAll = ref(false);
const products = ref([
{ id: 1, name: 'iPhone', sale: true },
{ id: 2, name: 'MacBook', sale: false },
{ id: 3, name: 'iPad', sale: true },
]);
const filtered = computed(() => {
return products.value.filter(p =>
p.name.toLowerCase().includes(search.value.toLowerCase())
);
});
</script>
<style scoped>
.sale {
color: red;
font-weight: bold;
}
</style>
Итог
- v-if/v-else/v-else-if — условный рендеринг
- v-show — скрытие через CSS
- v-for — циклы
- :key — обязателен
- Не совмещай v-for и v-if на одном элементе
Дальше — обработка событий.