Урок 04

Директивы: 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-ifv-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 на одном элементе

Дальше — обработка событий.