Олег Марков
Директива v-show во Vue.js
Введение
Директива v-show во Vue помогает управлять видимостью элементов на странице через реактивные данные. Проще говоря, вы можете показывать или скрывать блоки интерфейса, не удаляя их из DOM, а лишь меняя CSS-свойство display.
На первый взгляд v-show очень похожа на v-if, но под капотом они работают по-разному и дают разные эффекты по производительности и поведению компонента. Из-за этого у многих разработчиков возникает вопрос — что именно использовать и в каких ситуациях.
Здесь я подробно разберу, как работает v-show, как она взаимодействует с реактивностью Vue, чем отличается от v-if, как использовать ее в связке с анимациями и что нужно учитывать, чтобы не ловить неожиданные баги.
Базовый синтаксис и принцип работы v-show
Простое использование
Начнем с самого простого примера. Смотрите, я покажу вам, как выглядит минимальное использование v-show:
<template>
<div>
<!-- Кнопка переключает видимость блока -->
<button @click="isVisible = !isVisible">
Переключить блок
</button>
<!-- Блок с управляемой видимостью -->
<p v-show="isVisible">
Этот текст показывается и скрывается с помощью v-show
</p>
</div>
</template>
<script>
export default {
data() {
return {
// Булево значение управляет видимостью абзаца
isVisible: true
}
}
}
</script>
Здесь v-show принимает выражение isVisible. Когда isVisible равно true, элемент отображается, когда false — скрывается.
Важно понять ключевой момент: v-show не удаляет элемент из DOM. Вместо этого Vue просто добавляет или убирает CSS-свойство display:none.
Если вы посмотрите в инструментах разработчика браузера (DevTools), то увидите:
- Элемент существует в DOM всегда, независимо от значения isVisible.
- При isVisible = false элемент получает стиль display: none.
- При isVisible = true стиль display: none убирается (или меняется на исходный, например block, inline-block и т.д.).
Как именно Vue меняет display
Давайте посмотрим, как это выглядит в «развенчанном» виде:
<p v-show="isVisible">
Текст
</p>
После компиляции и во время выполнения Vue делает примерно следующее (упрощенно):
<p style="display: none;">
Текст
</p>
или
<p style="">
Текст
</p>
в зависимости от значения выражения.
Vue не пытается «восстанавливать» точное значение display (block, inline и т.д.). Браузер сам подставляет его, исходя из типа элемента. Это нужно помнить, если вы явно задаете display через CSS.
Отличия v-show от v-if
Ключевая разница — DOM и рендеринг
Давайте разберемся на примере. Ниже два похожих фрагмента: один с v-show, второй с v-if.
<!-- Используем v-show -->
<p v-show="visibleShow">
Элемент с v-show
</p>
<!-- Используем v-if -->
<p v-if="visibleIf">
Элемент с v-if
</p>
Основные отличия:
v-show:
- Элемент всегда присутствует в DOM.
- Vue только переключает display между none и нормальным значением.
- Реактивные привязки, слушатели событий и состояние внутренних компонентов не пересоздаются.
v-if:
- Элемент добавляется в DOM только если условие true.
- При false элемент полностью удаляется из DOM.
- При повторном true элемент создается заново, еще раз проходят все хуки жизненного цикла вложенных компонентов.
Влияние на производительность
Теперь давайте посмотрим, как это отражается на производительности:
v-show:
- Быстро переключает видимость.
- Нет дополнительных затрат на создание и уничтожение DOM-узлов.
- Но начальный рендер дороже, так как нужно отрендерить элемент сразу, даже если он скрыт.
v-if:
- На начальном рендере может быть дешевле, потому что не создает элемент, если условие false.
- Но каждый переключатель true/false приводит к созданию/удалению DOM-структуры.
- Это особенно ощутимо, если внутри условия сложный компонент с большим количеством узлов.
Из этого вытекает практическое правило:
- Если блок нужно часто показывать/скрывать — используйте v-show.
- Если блок редко появляется (например, модальное окно, открываемое изредка) — подумайте о v-if, чтобы не держать этот блок постоянно в DOM.
Когда стоит выбирать v-show
Частые переключения видимости
Типичный пример — вкладки (tabs). Смотрите, я покажу пример:
<template>
<div>
<!-- Простейшие вкладки -->
<button @click="activeTab = 'a'">Вкладка A</button>
<button @click="activeTab = 'b'">Вкладка B</button>
<!-- Содержимое вкладки A -->
<div v-show="activeTab === 'a'">
<!-- Здесь может быть сложная разметка -->
<p>Содержимое вкладки A</p>
</div>
<!-- Содержимое вкладки B -->
<div v-show="activeTab === 'b'">
<p>Содержимое вкладки B</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
activeTab: 'a' // Активная вкладка по умолчанию
}
}
}
</script>
В такой ситуации:
- Содержимое обеих вкладок сразу создается и присутствует в DOM.
- Переключение вкладок происходит моментально — Vue лишь меняет display.
Если бы вы использовали v-if, то при каждом переключении вкладки:
- Текущая вкладка уничтожалась бы.
- Новая вкладка создавалась бы с нуля.
- Все поля форм, состояние вложенных компонентов инициализировались бы заново.
Скрытие интерфейсных элементов без потери состояния
Представьте форму, где вы хотите временно скрывать часть полей, но при возвращении не терять введенные данные.
Например:
<template>
<form>
<!-- Базовые поля формы -->
<input v-model="form.name" placeholder="Имя" />
<input v-model="form.email" placeholder="Email" />
<label>
<input type="checkbox" v-model="showAdvanced" />
Показать дополнительные настройки
</label>
<!-- Дополнительные поля -->
<div v-show="showAdvanced">
<!-- Значения этих полей сохраняются, даже если блок скрыт -->
<input v-model="form.company" placeholder="Компания" />
<input v-model="form.position" placeholder="Должность" />
</div>
</form>
</template>
<script>
export default {
data() {
return {
showAdvanced: false, // Управление видимостью блока
form: {
name: '',
email: '',
company: '',
position: ''
}
}
}
}
</script>
Здесь v-show важен тем, что:
- Поля внутри скрываемого блока не уничтожаются.
- Значения form.company и form.position не теряются при каждом включении/выключении блока.
Если заменить v-show на v-if, то скрытие блока приведет к удалению всех внутренних элементов формы, а при повторном показе значения будут сброшены.
Реактивность и v-show
Как выражение внутри v-show обновляется
Выражение внутри v-show — обычное реактивное выражение. Оно может быть:
- Булевой переменной (isVisible).
- Результатом вычисляемого свойства (computed).
- Логическим выражением (count > 0 && isActive).
Давайте посмотрим пример с вычисляемым свойством:
<template>
<div>
<input v-model.number="count" type="number" />
<!-- Показываем блок только если число больше нуля -->
<p v-show="hasPositiveCount">
Число больше нуля
</p>
</div>
</template>
<script>
export default {
data() {
return {
count: 0 // Исходное значение числа
}
},
computed: {
hasPositiveCount() {
// Вычисляемое свойство возвращает булево значение
return this.count > 0
}
}
}
</script>
Как только вы меняете count, Vue автоматически пересчитывает hasPositiveCount и соответственно обновляет CSS display у элемента с v-show.
Взаимодействие с дочерними компонентами
Если вы применяете v-show к компоненту, он ведет себя так же, как к обычному элементу: компонент остается «живым», даже когда он скрыт.
Пример:
<template>
<div>
<button @click="showChild = !showChild">
Переключить компонент
</button>
<!-- Дочерний компонент просто скрывается -->
<ChildComponent v-show="showChild" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: { ChildComponent },
data() {
return {
showChild: true // Управление видимостью дочернего компонента
}
}
}
</script>
Поведение:
- Хуки mounted, created дочернего компонента вызываются один раз при первом рендеринге.
- При скрытии/показе с v-show хуки destroyed/unmounted не вызываются.
- Внутреннее состояние ChildComponent (данные, локальные переменные, v-model полей и т.д.) сохраняется между переключениями.
Комбинации v-show с CSS и классами
Совместное использование с классами и стилями
v-show управляет только display. Все остальные стили и классы вы можете применять как обычно.
Посмотрите пример:
<template>
<div>
<button @click="toggle">
Переключить уведомление
</button>
<p
v-show="visible"
:class="['notification', { 'notification--warning': isWarning }]"
:style="{ color: textColor }"
>
Важное уведомление
</p>
</div>
</template>
<script>
export default {
data() {
return {
visible: true, // Управляет v-show
isWarning: true, // Управляет классом
textColor: 'red' // Управляет инлайновым стилем
}
},
methods: {
toggle() {
this.visible = !this.visible // Меняем только видимость
}
}
}
</script>
Здесь:
- v-show отвечает за наличие или отсутствие display: none.
- :class и :style продолжают работать независимо.
- Когда элемент скрыт, классы и стили все равно «висят» на нем в DOM, просто сам элемент не отображается.
Конфликты с display в CSS
Иногда вы можете задать display через CSS, а затем скрывать элемент через v-show. Тогда важно помнить, что:
- Vue задает инлайновый стиль display: none, который имеет более высокий приоритет, чем display из файла CSS.
- Когда условие v-show становится true, Vue убирает инлайновый display, и элемент снова отображается в соответствии с вашими CSS-правилами.
Пример CSS:
.menu {
display: flex; /* Базовое отображение элемента */
}
И шаблон:
<div class="menu" v-show="isOpen">
<!-- Пункты меню -->
</div>
Поведение:
- При isOpen = false элемент получает style="display: none;" и скрывается.
- При isOpen = true атрибут style с display удаляется, и снова применится display: flex из CSS-класса menu.
v-show и анимации
Почему v-show не поддерживает transition по умолчанию
Если вы знакомы с Vue transition для v-if, вы можете ожидать похожее поведение и с v-show. Но есть нюанс: transition с v-show работает не так, как с v-if, и требует немного другого подхода.
С v-if Vue автоматически добавляет/удаляет элементы из DOM, и transition-хуки управляют этим процессом. С v-show элемент лишь получает/убирает display: none, и обычный CSS-переход по display не анимируется.
Поэтому чаще всего для анимаций с v-show используют переходы по opacity, height, transform и т.п.
Пример анимации с v-show и transition
Давайте посмотрим на практический пример:
<template>
<div>
<button @click="show = !show">
Переключить панель
</button>
<!-- Оборачиваем блок в transition -->
<transition name="fade">
<div v-show="show" class="panel">
Панель с анимацией видимости
</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true // Управляет v-show
}
}
}
</script>
<style scoped>
.panel {
padding: 16px;
background: #f0f0f0;
}
/* Базовая анимация по opacity */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease; /* Плавный переход прозрачности */
}
.fade-enter-from,
.fade-leave-to {
opacity: 0; /* Начальное и конечное состояние - прозрачный */
}
</style>
Как это работает:
- v-show мгновенно меняет display, но transition срабатывает по изменению классов, которые Vue добавляет при показе/скрытии.
- Мы анимируем opacity, а не display.
- Когда show становится false, Vue добавляет классы fade-leave-active и fade-leave-to, а затем скрывает элемент, выставляя display:none в конце анимации.
Типичные сценарии использования v-show
Переключение простых визуальных состояний
Простейший пример — «Показать еще/Скрыть»:
<template>
<div>
<p>
Краткий текст…
</p>
<a href="#" @click.prevent="expanded = !expanded">
{{ expanded ? 'Скрыть подробности' : 'Показать подробнее' }}
</a>
<div v-show="expanded">
<!-- Дополнительная информация -->
<p>
Здесь находится расширенное описание, которое не всегда нужно показывать.
</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
expanded: false // Состояние "развернуто" / "свернуто"
}
}
}
</script>
В этом случае:
- Пользователь часто переключает видимость блока.
- Данные внутри блока не должны каждый раз пересоздаваться.
- v-show подходит лучше, чем v-if.
Индикаторы загрузки и спиннеры
Здесь важно не делать лишних дорогостоящих операций. Но если сам спиннер — простой элемент, то v-show отлично подходит:
<template>
<div>
<button @click="load">
Загрузить данные
</button>
<!-- Крутим спиннер во время загрузки -->
<div v-show="isLoading" class="spinner">
Загрузка...
</div>
<div v-if="data">
<!-- Выводим загруженные данные -->
{{ data }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
isLoading: false, // Флаг загрузки
data: null // Загружаемые данные
}
},
methods: {
async load() {
this.isLoading = true // Показываем спиннер
try {
// Здесь имитация запроса к серверу
const response = await new Promise(resolve => {
setTimeout(() => resolve('Результат запроса'), 1000)
})
this.data = response // Сохраняем данные
} finally {
this.isLoading = false // Скрываем спиннер
}
}
}
}
</script>
- Спиннер всегда существует в DOM.
- Переключение isLoading только показывает/скрывает его.
Ограничения и подводные камни v-show
Нельзя использовать на template
В отличие от v-if, директива v-show не может применяться к тегу template. Это логично: v-show управляет стилем конкретного DOM-элемента, а template в итоговом DOM вообще не существует.
Так сделать нельзя:
<!-- Ошибка: v-show на template работать не будет -->
<template v-show="visible">
<p>Один</p>
<p>Два</p>
</template>
Vue проигнорирует v-show на template, потому что не к чему применять display.
Если вам нужно скрыть несколько элементов сразу, оберните их в реальный контейнер:
<div v-show="visible">
<p>Один</p>
<p>Два</p>
</div>
Или используйте v-if на template, если вам важно именно создавать/удалять элементы:
<template v-if="visible">
<p>Один</p>
<p>Два</p>
</template>
Влияние на доступность (ARIA и фокус)
Даже когда элемент скрыт через v-show (display:none), он:
- Не участвует в фокусировке с клавиатуры (tab).
- Не озвучивается скринридерами.
- Ведет себя так же, как обычный элемент с CSS display:none.
Но есть нюанс: если вы работаете со сложной доступностью (ARIA-атрибуты, управление фокусом вручную), важно помнить, что элемент все равно «живет» в памяти и в Vue-компоненте. Вы можете случайно:
- Программно установить фокус на скрытый элемент.
- Оперировать ссылками на DOM-узлы, которые скрыты, но вы об этом забыли.
Поэтому старайтесь:
- Явно проверять видимость перед тем, как, например, вызывать element.focus().
- Согласовывать состояние v-show с ARIA-атрибутами (aria-hidden и т.п.), если вы их используете.
Практические рекомендации по выбору между v-show и v-if
Простое правило выбора
Можно запомнить короткое практическое правило:
Если элемент или блок:
- часто переключается,
- должен сохранять внутреннее состояние,
- не слишком тяжелый по количеству DOM-узлов —
тогда используйте v-show.
Если:
- элемент редко показывается,
- содержит много вложенных компонентов,
- имеет сложную разметку с множеством слушателей событий,
- должен освобождать ресурсы при скрытии —
тогда лучше использовать v-if.
Комбинирование v-if и v-show
Иногда полезно комбинировать оба подхода. Например:
- v-if — чтобы вообще не создавать компонент до определенного момента (например, до первого открытия).
- v-show — чтобы потом быстро переключать его видимость.
Покажу вам, как это реализовано на практике:
<template>
<div>
<button @click="toggleModal">
Открыть / закрыть модальное окно
</button>
<!-- Создаем модальное окно только после первого открытия -->
<Modal
v-if="modalCreated"
v-show="modalVisible"
@close="modalVisible = false"
/>
</div>
</template>
<script>
import Modal from './Modal.vue'
export default {
components: { Modal },
data() {
return {
modalCreated: false, // Отвечает за создание компонента
modalVisible: false // Отвечает за видимость (через v-show)
}
},
methods: {
toggleModal() {
if (!this.modalCreated) {
// Создаем модальное окно при первом открытии
this.modalCreated = true
}
// Переключаем видимость
this.modalVisible = !this.modalVisible
}
}
}
</script>
Логика:
- До первого открытия Modal не существует в DOM вообще.
- При первом открытии modalCreated становится true, и компонент рендерится.
- Дальше мы только скрываем/показываем его через v-show, не уничтожая состояние.
Заключение
Директива v-show во Vue — это удобный инструмент для управления видимостью элементов через CSS display, без удаления их из DOM. Она отлично подходит для сценариев, где важно:
- быстро переключать отображение без лишних затрат,
- сохранять внутреннее состояние элементов и компонентов между показами,
- работать с часто меняющимися визуальными состояниями интерфейса.
Ключевые моменты, которые стоит зафиксировать:
- v-show не удаляет элемент, а только ставит/убирает display:none.
- v-if создает и уничтожает элементы, поэтому подходит для «редких» блоков.
- v-show нельзя использовать на template, только на реальных DOM-элементах или компонентах.
- При работе с v-show важно помнить о возможных нюансах с CSS display и доступностью.
Используя v-show осознанно и понимая, как она работает изнутри, вы сможете строить интерфейсы, которые одновременно остаются отзывчивыми, предсказуемыми и удобными для сопровождения.
Частозадаваемые технические вопросы по теме статьи и ответы на них
Можно ли использовать v-show вместе с v-else или v-else-if
Нет, у v-show нет пары v-else или v-else-if. Эти конструкции работают только с v-if. Если вам нужно условное ветвление, используйте v-if/v-else-if/v-else. v-show — это только про видимость одного конкретного элемента.
Как правильно тестировать логику с v-show во Vue Test Utils
В тестах лучше проверять не наличие элемента в DOM, а его стиль. Пример мини-инструкции:
- Рендерите компонент через mount.
- Меняйте реактивное состояние, влияющее на v-show.
- Используйте expect(wrapper.find('селектор').isVisible()).toBe(true/false).
- Либо проверяйте style.display при необходимости.
Почему элемент с v-show занимает место в DOM даже когда скрыт
Потому что v-show использует display:none, а не удаляет элемент. Если вы хотите, чтобы элемент вообще не создавался и не занимал место, используйте v-if. v-show только управляет отображением, но не существованием элемента.
Как сделать плавное скрытие по высоте с v-show
Обычный transition по height требует явного CSS:
- Оберните блок в transition.
- Используйте CSS-классы с overflow:hidden и переходом по max-height.
- В JS v-show остается как есть, а анимация выполняется за счет CSS.
Можно ли динамически навешивать и снимать v-show
Саму директиву динамически удалить нельзя, но можно менять ее выражение. Например, вместо v-show="flag" используйте вычисляемое свойство, которое в некоторых состояниях всегда возвращает true. Тогда поведение v-show фактически «отключается», хотя сама директива остается в шаблоне.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

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