Олег Марков
Директива v-if во Vue
Введение
Директива v-if во Vue отвечает за условный рендеринг элементов и компонентов. С ее помощью вы можете говорить фреймворку когда именно узел должен попадать в DOM, а когда его не должно существовать вообще.
Задача v-if проста на первый взгляд – показывать или скрывать куски интерфейса в зависимости от состояния. Но под этой простотой скрывается несколько важных нюансов, которые напрямую влияют на архитектуру приложения, производительность и удобство поддержки кода.
В этой статье вы увидите:
- как устроен синтаксис v-if
- чем отличается v-if от v-show и когда что использовать
- как правильно комбинировать v-if, v-else и v-else-if
- как работать с v-if и списками через v-for
- как v-if влияет на состояние компонентов и обработчики событий
- как выносить сложную логику условий в вычисляемые свойства и методы
Давайте разбираться на примерах и смотреть, как это работает на практике.
Базовый синтаксис v-if
Простое условие
Начнем с самого простого варианта. Мы хотим показать блок только если некоторое условие истинно.
<template>
<!-- Сообщение отображается, только если isLoggedIn === true -->
<p v-if="isLoggedIn">
Добро пожаловать
</p>
<!-- Сообщение отображается, только если isLoggedIn === false -->
<p v-if="!isLoggedIn">
Пожалуйста, войдите в систему
</p>
</template>
<script>
export default {
data() {
return {
isLoggedIn: false, // Флаг авторизации пользователя
};
},
};
</script>
Ключевой момент здесь в том, что если условие v-if="isLoggedIn" ложно, Vue вообще не создает этот элемент в DOM. Это не скрытие через стили, а реальное отсутствие узла.
Что именно принимает v-if
Вы можете передавать в v-if любое выражение, которое в итоге даёт булево значение:
- булевая переменная:
v-if="isVisible" - сравнение:
v-if="count > 0" - логическое сочетание:
v-if="isAdmin && isActive" - проверка существования:
v-if="user && user.profile"
Пример:
<div v-if="items.length > 0 && !isLoading">
<!-- Здесь показываем список, если есть элементы и нет загрузки -->
Список загружен
</div>
Vue не накладывает особых ограничений на выражения, но стоит помнить о читаемости. Если условие становится слишком длинным и сложным, обычно имеет смысл вынести его в вычисляемое свойство. Чуть ниже я покажу, как это сделать.
Комбинация v-if, v-else и v-else-if
В реальном интерфейсе редко бывает одинокий v-if. Обычно нужно вывести один из нескольких вариантов: например, состояние загрузки, ошибку или данные. Для этого Vue предоставляет связку директив.
v-if + v-else
Смотрите, я покажу вам самый простой вариант: условие и альтернативный блок.
<template>
<!-- Если isLoggedIn === true -->
<p v-if="isLoggedIn">
Вы вошли в систему
</p>
<!-- Если isLoggedIn === false -->
<p v-else>
У вас нет доступа
</p>
</template>
<script>
export default {
data() {
return {
isLoggedIn: false, // Состояние авторизации
};
},
};
</script>
Правила для v-else:
- он всегда должен идти сразу после v-if или v-else-if
- у v-else не бывает выражения, он срабатывает как «иначе» для предыдущего условия
Если между v-if и v-else встанет хоть один лишний элемент, даже комментарий HTML, цепочка сломается.
v-if + v-else-if + v-else
Теперь давайте посмотрим цепочку из нескольких состояний. Например, у нас есть три варианта:
- еще идет загрузка
- произошла ошибка
- данные успешно получены
<template>
<!-- Состояние загрузки -->
<p v-if="isLoading">
Загрузка данных...
</p>
<!-- Состояние ошибки -->
<p v-else-if="error">
Произошла ошибка при загрузке
</p>
<!-- Успешное состояние -->
<div v-else>
<!-- Здесь мы уже показываем контент -->
<p>Данные загружены успешно</p>
</div>
</template>
<script>
export default {
data() {
return {
isLoading: true, // Идет ли загрузка
error: null, // Текст ошибки или null если ошибки нет
};
},
};
</script>
Логика цепочки такая:
- Vue проверяет первое v-if
- если оно ложно – переходит к следующему v-else-if
- если в цепочке нет ни одного истинного условия – срабатывает v-else
Важно, что выполняется только один блок из всей цепочки. Остальные в DOM не попадают.
Соответствие уровню вложенности
Каждый v-else и v-else-if относится к ближайшему предшествующему v-if или v-else-if на том же уровне вложенности.
Пример, где это может сыграть злую шутку:
<!-- Плохой пример - v-else относится не к тому v-if -->
<div v-if="outer">
<p v-if="inner">
Внутренний текст
</p>
</div>
<p v-else>
Альтернативный текст
</p>
Здесь v-else будет пытаться «привязаться» к ближайшему v-if, но он внутри другого блока, поэтому возникнет ошибка. Правильная цепочка должна находиться рядом:
<!-- Правильный пример - цепочка на одном уровне -->
<div v-if="outer">
<p v-if="inner">
Внутренний текст
</p>
</div>
<div v-else>
Альтернативный текст
</div>
Обратите внимание, что v-if и v-else должны быть соседними элементами на одном уровне.
Как работает v-if под капотом
Важно понять: v-if управляет не видимостью, а существованием узла. Это влияет сразу на несколько аспектов.
Создание и уничтожение DOM-узлов
Когда условие становится истинным:
- Vue создает новый DOM-узел
- инициализирует его
- навешивает обработчики событий
- запускает жизненный цикл компонента (если это компонент)
Когда условие становится ложным:
- Vue удаляет узел из DOM
- снимает обработчики событий
- для компонентов вызывает хуки уничтожения (beforeUnmount, unmounted во Vue 3)
Таким образом, при переключении флага Vue как бы «пересоздает» куски интерфейса.
Влияние на состояние компонентов
Теперь давайте посмотрим, как это отражается на состоянии.
<template>
<button @click="showChild = !showChild">
Переключить компонент
</button>
<!-- Дочерний компонент будет создаваться и уничтожаться -->
<ChildComponent v-if="showChild" />
</template>
<script>
import ChildComponent from "./ChildComponent.vue";
export default {
components: { ChildComponent },
data() {
return {
showChild: true, // Флаг отображения дочернего компонента
};
},
};
</script>
В ChildComponent:
export default {
data() {
return {
counter: 0, // Локальное состояние компонента
};
},
mounted() {
// Этот хук выполнится каждый раз,
// когда компонент будет показан через v-if
console.log("ChildComponent создан");
},
unmounted() {
// Этот хук выполнится каждый раз,
// когда компонент будет удален из DOM
console.log("ChildComponent уничтожен");
},
};
Если вы нажмете кнопку, чтобы скрыть компонент, а потом снова показать, состояние counter начнет с нуля, потому что компонент пересоздается.
Это важно, когда:
- вы хотите, чтобы состояние сбрасывалось при каждом показе
- или наоборот, хотите сохранить состояние – тогда v-if может быть не лучшим выбором, и стоит подумать о v-show или
keep-alive(для динамических компонентов).
v-if против v-show
Очень часто вопрос звучит так: когда нужен v-if, а когда v-show. Поведение этих директив разное, и это заметно влияет на производительность и поведение приложения.
Как работает v-show
v-show управляет только CSS-свойством display. Узел всегда присутствует в DOM, но при ложном условии скрывается.
<p v-show="isVisible">
Этот текст всегда в DOM, но может быть скрыт стилем
</p>
Если условие isVisible меняется, Vue просто переключает display между "none" и нормальным значением. Компонент не уничтожается и не создается заново.
Сравнение v-if и v-show
Давайте сведем различия:
- v-if
- удаляет/создает узлы в DOM
- дороже при частых переключениях
- дешевле, если условие редко меняется
- компоненты заново проходят жизненный цикл при каждом показе
- v-show
- только скрывает/показывает через CSS
- дешевле при частых переключениях
- дороже при первичной инициализации, если элементов много
- состояние компонента сохраняется между показами
Типичная рекомендация:
- если элемент показывается/скрывается редко (например, модальное окно с формой, которое открывается изредка) – используйте v-if
- если элемент переключается часто (например, табы, раскрытие/сворачивание блоков) – используйте v-show
Пример для сравнения:
<!-- Модальное окно - чаще всего v-if -->
<Modal v-if="isModalOpen" />
<!-- Переключение вкладок - удобно через v-show -->
<div>
<button @click="activeTab = 'info'">Инфо</button>
<button @click="activeTab = 'settings'">Настройки</button>
<section v-show="activeTab === 'info'">
<!-- Здесь контент вкладки Инфо -->
</section>
<section v-show="activeTab === 'settings'">
<!-- Здесь контент вкладки Настройки -->
</section>
</div>
Как видите, поведение очень разное. v-if – это логика существования, v-show – логика видимости.
v-if и v-for: порядок имеет значение
Отдельная тема – использование v-if вместе с v-for. Здесь есть важная тонкость, на которую часто наталкиваются разработчики.
Почему нельзя просто смешать v-if и v-for на одном элементе
Например, хочется сделать так:
<!-- Пример, которого лучше избегать -->
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
Vue выполняет v-for с более высоким приоритетом, чем v-if. Это значит, что:
- сначала создается список экземпляров для всех users
- затем к каждому из них применяется v-if
В итоге вы получаете потенциально лишние вычисления и более сложную логику. Кроме того, код становится менее очевидным.
Правильный подход: фильтрация данных
Чаще всего лучше отфильтровать данные заранее. Давайте разберемся на примере.
<template>
<!-- Здесь мы итерируемся уже по отфильтрованному массиву -->
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</template>
<script>
export default {
data() {
return {
users: [
// Здесь массив пользователей
// Каждый объект имеет поле isActive
],
};
},
computed: {
activeUsers() {
// Здесь мы возвращаем только активных пользователей
return this.users.filter((user) => user.isActive);
},
},
};
</script>
Мы вынесли логику в вычисляемое свойство activeUsers. Так код становится понятнее и эффективнее.
Когда v-if «оборачивает» v-for
Другой вариант – разместить v-if выше по дереву, чтобы контролировать весь список целиком.
<template>
<!-- Показываем весь список только если он не пустой -->
<ul v-if="users.length > 0">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
<p v-else>
Пользователей нет
</p>
</template>
Здесь v-if относится к самому списку, а v-for применяется уже внутри.
Использование шаблонного контейнера template
Если нужно применить v-if к группе элементов внутри v-for, удобнее использовать специальный тег template.
<template>
<ul>
<!-- Здесь мы делаем одну итерацию на template,
а внутри уже несколько элементов -->
<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
<li v-else>
{{ user.name }} (неактивен)
</li>
</template>
</ul>
</template>
Тег template сам в DOM не попадает, он служит «невидимым контейнером». Это помогает правильно сочетать v-for с цепочками v-if / v-else без лишних оберток.
Сложные условия и вычисляемые свойства
Когда условие в v-if становится многословным, чтение шаблона усложняется. В таких случаях лучше вынести логику в computed или метод. Давайте посмотрим, как это выглядит.
Вычисляемое свойство для условия
Представим, что блок должен показываться только если:
- пользователь авторизован
- его роль – администратор
- и у него не истек срок действия подписки
Вместо длинного выражения в шаблоне:
<!-- Длинное мало читаемое условие -->
<div v-if="user && user.isLoggedIn && user.role === 'admin' && !user.isExpired">
Админ панель
</div>
Удобнее сделать так:
<template>
<!-- Условие читается как «готовое» -->
<div v-if="canShowAdminPanel">
Админ панель
</div>
</template>
<script>
export default {
data() {
return {
user: null, // Объект пользователя или null если не авторизован
};
},
computed: {
canShowAdminPanel() {
// Здесь мы явно описываем все проверки
if (!this.user) return false;
if (!this.user.isLoggedIn) return false;
if (this.user.role !== "admin") return false;
if (this.user.isExpired) return false;
return true;
},
},
};
</script>
Теперь условие в шаблоне простое и читаемое, а вся логика сосредоточена в одном месте, где ее легко дополнять и отлаживать.
Условия в методах
Если проверка требует побочных эффектов или зависит от параметров, ее удобнее вынести в метод и передавать аргументы.
<template>
<ul>
<li
v-for="product in products"
:key="product.id"
v-if="shouldShowProduct(product)"
>
{{ product.name }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
products: [], // Список товаров
searchQuery: "", // Поисковая строка
showOnlyAvailable: true, // Флаг фильтра по наличию
};
},
methods: {
shouldShowProduct(product) {
// Проверяем соответствие поисковой строке
const matchesSearch =
product.name.toLowerCase().includes(this.searchQuery.toLowerCase());
// Проверяем фильтр по наличию
const matchesAvailability = this.showOnlyAvailable
? product.inStock // Показываем только если товар в наличии
: true; // Если фильтр не включен, пропускаем этот шаг
return matchesSearch && matchesAvailability;
},
},
};
</script>
В этом примере v-if использует метод shouldShowProduct, который инкапсулирует логику фильтрации для каждого товара.
v-if и динамические компоненты
Директива v-if особенно полезна при работе с динамическими компонентами, когда вам нужно решать, какой компонент отрендерить в зависимости от состояния.
Простой переключатель компонентов
Теперь вы увидите, как это выглядит в коде, когда мы показываем один из двух компонентов в зависимости от флага.
<template>
<button @click="mode = 'view'">
Режим просмотра
</button>
<button @click="mode = 'edit'">
Режим редактирования
</button>
<!-- Показываем один компонент или другой -->
<ViewComponent v-if="mode === 'view'" />
<EditComponent v-else-if="mode === 'edit'" />
<p v-else>
Неизвестный режим
</p>
</template>
<script>
import ViewComponent from "./ViewComponent.vue";
import EditComponent from "./EditComponent.vue";
export default {
components: {
ViewComponent,
EditComponent,
},
data() {
return {
mode: "view", // Текущий режим - view или edit
};
},
};
</script>
Здесь v-if управляет тем, какой именно компонент будет существовать в DOM в текущий момент.
Состояние при переключении
Важно помнить, что при переключении между компонентами через v-if состояние компонента, который был скрыт, теряется, так как он уничтожается.
Если вам нужно сохранять состояние, можно использовать динамический компонент с keep-alive и переключать тип компонента, а не уничтожать его. Но это уже тема ближе к управлению динамическими компонентами, хотя v-if там тоже часто задействован для дополнительных проверок.
Влияние v-if на обработчики событий и привязки
Когда вы используете v-if, связанный с ним элемент исчезает и появляется заново. Это значит, что:
- все обработчики событий, навешанные Vue, снимаются и привязываются заново
- все привязки к данным пересоздаются вместе с узлом
- любые ссылки на DOM-элементы через ref становятся недействительными при уничтожении и создаются снова при рендере
Давайте разберемся на примере с ref.
<template>
<button @click="toggle">
Переключить блок
</button>
<div v-if="show" ref="panel">
Панель
</div>
</template>
<script>
export default {
data() {
return {
show: true, // Флаг отображения панели
};
},
methods: {
toggle() {
this.show = !this.show; // Переключаем флаг
},
focusPanel() {
// Здесь мы обращаемся к DOM-элементу через ref
if (this.$refs.panel) {
this.$refs.panel.focus();
}
},
},
};
</script>
Здесь важно помнить: когда show === false, ссылка this.$refs.panel будет undefined, потому что элемент не существует в DOM. Поэтому перед использованием ref в сочетании с v-if стоит всегда проверять, что он реально есть.
Типичные практические сценарии использования v-if
Давайте посмотрим на несколько распространенных ситуаций и разберем, как там обычно применяют v-if.
Отображение по ролям и правам
Очень часто v-if используют для контроля доступа к частям интерфейса.
<template>
<!-- Кнопка видна только администратору -->
<button v-if="user && user.role === 'admin'">
Удалить пользователя
</button>
</template>
Часто эту логику выносят в отдельный помощник (например, метод hasPermission), чтобы не дублировать условия.
Пошаговые мастера и формы
Пошаговые формы (wizard) – еще один пример, где v-if помогает показывать только один шаг за раз.
<template>
<div v-if="step === 1">
<!-- Шаг 1 регистрации -->
</div>
<div v-else-if="step === 2">
<!-- Шаг 2 регистрации -->
</div>
<div v-else-if="step === 3">
<!-- Шаг 3 регистрации -->
</div>
<div v-else>
<!-- Завершение -->
</div>
<button @click="nextStep">
Далее
</button>
</template>
<script>
export default {
data() {
return {
step: 1, // Текущий шаг мастера
};
},
methods: {
nextStep() {
// Переходим к следующему шагу, пока не достигнем последнего
if (this.step < 4) {
this.step += 1;
}
},
},
};
</script>
Здесь v-if по сути управляет всей навигацией между шагами.
Обработка ошибок и пустых состояний
Нередко в одном месте показывают контент, состояние загрузки и пустой результат. Давайте разберемся на примере.
<template>
<!-- Загрузка -->
<p v-if="isLoading">
Загрузка...
</p>
<!-- Ошибка -->
<p v-else-if="error">
Ошибка - {{ error }}
</p>
<!-- Пустое состояние -->
<p v-else-if="items.length === 0">
Ничего не найдено
</p>
<!-- Основной контент -->
<ul v-else>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
Такая цепочка хорошо описывает разные состояния одного и того же участка интерфейса.
Заключение
Директива v-if во Vue – это основной инструмент условного рендеринга, который управляет именно существованием элементов и компонентов в DOM, а не просто их видимостью. Через v-if вы можете:
- показывать или полностью убирать куски интерфейса
- строить цепочки условий с v-else-if и v-else
- управлять создаваемыми и уничтожаемыми компонентами
- реализовывать разные состояния одного участка интерфейса – загрузка, ошибка, данные
Важно понимать разницу между v-if и v-show. v-if создает и уничтожает узлы, поэтому подходит для редко меняющихся состояний и помогает экономить ресурсы, когда блок большинству пользователей вообще не нужен. v-show просто скрывает элемент через CSS, оставляя его в DOM, и лучше подходит для частых переключений.
Особого внимания заслуживает сочетание v-if с v-for. Лучше избегать одновременного использования этих директив на одном и том же элементе, выносить фильтрацию в вычисляемые свойства и по возможности размещать v-if выше по дереву, контролируя блок целиком.
Хорошей практикой является вынос сложных условий в computed-свойства или методы. Так шаблон остается читабельным, а логика централизована и легко тестируется.
Если вы будете относиться к v-if как к инструменту управления жизненным циклом части интерфейса, а не просто как к «спрятать/показать», станет проще проектировать архитектуру компонентов, избегать неожиданных потерь состояния и строить предсказуемое поведение приложения.
Частозадаваемые технические вопросы по теме статьи и ответы на них
Как избежать мигания контента при использовании v-if на серверном рендеринге
При SSR важно, чтобы условия на сервере и клиенте совпадали. Используйте одни и те же исходные данные для рендера и избегайте условий, зависящих только от окружения браузера (например, window). Если нужно условие на основе среды, делайте проверку в хуке mounted, а не напрямую в v-if, и инициализируйте флаг по умолчанию так, чтобы разметка сервера и клиента совпадала.
Почему v-if внутри компонента не срабатывает при изменении пропса
Часто проблема в том, что пропс мутируется напрямую, а не через реактивное состояние родителя. Убедитесь, что родитель передает пропс как реактивное значение и обновляет его через data или computed с сеттером. Внутри дочернего компонента избегайте прямой записи в пропс, а используйте локальное состояние или событие для уведомления родителя.
Как использовать v-if с асинхронными запросами чтобы избежать состояния гонки
При нескольких быстрых запросах подряд старые ответы могут приходить позже новых. Храните идентификатор запроса или метку времени и в обработчике проверяйте, что это актуальный ответ, прежде чем менять состояние, от которого зависит v-if. Либо отменяйте предыдущие запросы, если библиотека HTTP это поддерживает.
Почему v-if срабатывает но переходы анимации не отрабатывают
Для анимаций с v-if обязательно оберните условный блок в компонент transition и убедитесь, что указаны корректные CSS-классы или настроена JavaScript-анимация. Пример
<transition name="fade">
<div v-if="visible">
Контент
</div>
</transition>
Также проверьте, что transition оборачивает ровно один корневой элемент и у name="fade" действительно есть определенные классы .fade-enter-active и .fade-leave-active.
Как организовать несколько независимых v-if цепочек в одном родителе
Каждая цепочка v-if v-else-if v-else должна быть локальной и идти подряд на одном уровне. Для нескольких независимых наборов условий используйте обертки div или template для каждой цепочки. Это помогает избежать пересечения условий и делает структуру шаблона более предсказуемой и понятной.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев