Олег Марков
Отображение компонента mounted - практическое руководство
Введение
Этап mounted (он же "компонент смонтирован" или "компонент отображен") — это ключевой момент жизненного цикла компонента во многих фронтенд‑фреймворках, прежде всего во Vue. В этот момент разметка уже добавлена в DOM, все реактивные данные компонента и привязки готовы, и вы можете безопасно обращаться к реальным DOM‑элементам.
Смотрите, я покажу вам, как лучше понимать этот этап и использовать его по назначению. Мы разберем:
- когда именно вызывается mounted;
- что к этому моменту уже доступно;
- что делать в mounted правильно, а что лучше вынести в другие хуки;
- типичные ошибки и способы их избежать;
- практические примеры с DOM, событиями, запросами на сервер и интеграцией сторонних библиотек.
Давайте двигаться шаг за шагом, чтобы картина этапа mounted стала максимально понятной и предсказуемой.
Жизненный цикл компонента и место mounted
Краткий обзор жизненного цикла
Чтобы правильно использовать mounted, важно понимать, где он находится в общей последовательности событий жизненного цикла.
На примере Vue (2 и 3 концептуально похожи) порядок в упрощенном виде выглядит так:
Инициализация:
- создание экземпляра компонента;
- настройка реактивности данных и вычисляемых свойств;
- применение опций компонента.
Хуки до монтирования:
- beforeCreate;
- created;
- beforeMount.
Монтирование в DOM:
- генерация виртуального DOM;
- рендеринг разметки;
- вставка в реальный DOM.
Хук mounted — компонент уже в DOM.
Обновления:
- beforeUpdate;
- обновление DOM;
- updated.
Размонтирование:
- beforeUnmount / beforeDestroy;
- unmounted / destroyed.
Хук mounted относится именно к моменту, когда DOM‑дерево компонента уже вставлено в документ, а значит:
- можно использовать ссылки на элементы (refs);
- можно измерять размеры блоков, позиции, прокрутку;
- можно инициализировать виджеты библиотек, которым нужно "живое" DOM‑дерево.
Когда вызывается mounted
Важно понимать два момента:
- mounted вызывается один раз за жизненный цикл конкретного экземпляра компонента — в момент первого монтирования.
- Он вызывается после того, как:
- прошла инициализация данных;
- прошел первый рендер;
- результат вставлен в DOM.
То есть, если вы выведите данные из компонента в консоль внутри mounted, вы получите уже готовые значения и сможете получить доступ к DOM через this.$el или refs.
Что доступно внутри mounted
Доступ к DOM‑элементам
Главное отличие mounted от created и других "ранних" хуков — возможность безопасно работать с DOM.
Внутри mounted доступны:
- корневой элемент компонента: this.$el;
- все refs (например, this.$refs.inputField);
- любые DOM‑методы, основанные на реальном расположении элементов (getBoundingClientRect, offsetWidth, scrollHeight и т.д.).
Давайте разберемся на простом примере.
export default {
name: 'ExampleComponent',
data() {
return {
width: 0, // Ширина блока
height: 0, // Высота блока
}
},
mounted() {
// Здесь компонент уже находится в DOM
// this.$el — корневой DOM-элемент компонента
const rect = this.$el.getBoundingClientRect()
// Сохраняем размеры в реактивные данные
this.width = rect.width
this.height = rect.height
// Теперь эти размеры можно использовать в шаблоне
}
}
Комментарии в коде подсказывают, что именно происходит: в mounted мы измеряем элемент и сохраняем размеры в реактивные свойства.
Доступ к реактивным данным и вычисляемым свойствам
К моменту mounted:
- все data и computed уже инициализированы;
- watchers уже зарегистрированы;
- методы доступны из this.
Это означает, что вы можете:
- обращаться к любым полям data;
- вызывать методы компонента;
- запускать дополнительные вычисления.
Например:
export default {
data() {
return {
items: [],
ready: false,
}
},
computed: {
itemsCount() {
// Вычисляем количество элементов
return this.items.length
}
},
mounted() {
// Обращаемся к вычисляемому свойству
console.log('Начальное количество элементов', this.itemsCount)
// Обновляем данные
this.items = [1, 2, 3]
// Помечаем, что компонент полностью инициализирован
this.ready = true
}
}
Здесь вы видите, что в mounted можно уже работать и с рассчитанными значениями, и с обновлением состояния.
Когда лучше не использовать mounted
Хотя в mounted доступно многое, есть вещи, которые лучше делать раньше:
- Получение исходных данных, не зависящих от DOM (например, запрос конфигурации) — лучше в created или setup.
- Инициализацию глобальных состояний или стораджей (Vuex / Pinia) — также до mounted.
Причина простая: mounted ждёт рендеринга компонента, а это требует дополнительных ресурсов. Если данные можно подготовить до отрисовки, компонент отобразится быстрее и с уже готовым содержимым.
Типичные задачи, которые удобно решать в mounted
Работа с реальным DOM и refs
Один из самых частых сценариев — работа с refs: фокус на поле ввода, скролл контейнера, управление кастомными элементами.
Давайте посмотрим на пример:
<template>
<input
ref="searchInput"
type="text"
placeholder="Начните вводить запрос"
>
</template>
<script>
export default {
name: 'SearchInput',
mounted() {
// Здесь я размещаю пример, чтобы вам было проще понять.
// Мы автоматически фокусируем поле после монтирования
this.$refs.searchInput.focus()
}
}
</script>
Комментарии поясняют: после монтирования компонент ставит курсор в поле ввода. Если попытаться сделать это раньше (например, в created), ссылка this.$refs.searchInput будет еще пустой.
Еще пример со скроллом:
<template>
<div ref="container" class="list-container">
<!-- Здесь много элементов списка -->
</div>
</template>
<script>
export default {
mounted() {
// Прокручиваем контейнер в самый низ после отображения
const container = this.$refs.container
container.scrollTop = container.scrollHeight
}
}
</script>
Опять же, такие операции возможны только после того, как DOM реально появится.
Инициализация сторонних библиотек
Частая задача — интегрировать:
- слайдеры;
- графики;
- редакторы текста;
- другие JS‑виджеты, которые ждут реальный DOM.
Покажу вам, как это выглядит в коде.
<template>
<div ref="chartContainer" class="chart"></div>
</template>
<script>
import { createChart } from 'some-chart-lib' // Условная библиотека
export default {
name: 'ChartComponent',
data() {
return {
chartInstance: null, // Сохраняем экземпляр графика
}
},
mounted() {
// Создаем график на базе реального DOM-элемента
this.chartInstance = createChart(this.$refs.chartContainer, {
// Конфигурация графика
type: 'line',
data: [1, 2, 3],
})
},
beforeUnmount() {
// Важно корректно уничтожить график, чтобы не было утечек памяти
if (this.chartInstance) {
this.chartInstance.destroy()
}
}
}
</script>
Здесь mounted отвечает за инициализацию после появления контейнера в DOM, а beforeUnmount — за корректное завершение работы библиотеки.
Подключение обработчиков событий окна и документа
Еще одна задача, для которой удобно использовать mounted, — добавление глобальных обработчиков:
- resize окна;
- scroll страницы;
- keydown / keyup на документе.
Давайте разберемся на примере отслеживания ширины окна:
export default {
data() {
return {
windowWidth: window.innerWidth, // Текущая ширина окна
}
},
methods: {
onResize() {
// Обновляем значение при каждом событии resize
this.windowWidth = window.innerWidth
}
},
mounted() {
// Добавляем обработчик после монтирования
window.addEventListener('resize', this.onResize)
},
beforeUnmount() {
// Обязательно удаляем обработчик, чтобы избежать утечек
window.removeEventListener('resize', this.onResize)
}
}
Обратите внимание, как этот фрагмент кода решает задачу: mounted добавляет обработчик, beforeUnmount снимает его.
Асинхронные операции в mounted
Можно ли делать запросы к API в mounted
Да, можно. В mounted уже доступны данные, поэтому, например, вы можете:
- сделать запрос, зависящий от размеров блока;
- отправить событие аналитики, когда компонент реально показан пользователю;
- загрузить дополнительные данные "лениво" после появления компонента.
Однако важно разграничивать ситуации:
- Если данные нужны, чтобы компонент вообще имел что отрисовать — лучше запрашивать их до монтирования, например в created или в родительском уровне (и передавать через пропсы).
- Если данные вторичны и не критичны для начального рендера — их удобно запрашивать в mounted.
Вот пример "ленивой" загрузки:
export default {
data() {
return {
stats: null, // Дополнительная статистика
loading: false,
error: null,
}
},
async mounted() {
// Здесь мы загружаем данные после того, как компонент появился на экране
this.loading = true
try {
const response = await fetch('/api/stats')
// Проверяем успешность ответа
if (!response.ok) {
throw new Error('Ошибка загрузки статистики')
}
const data = await response.json()
// Сохраняем результат
this.stats = data
} catch (e) {
// Обрабатываем ошибку
this.error = e.message
} finally {
// Обновляем флаг загрузки в любом случае
this.loading = false
}
}
}
Комментарии поясняют важные шаги: старт загрузки, обработка ошибки, завершение в finally.
Взаимодействие с родительскими компонентами
Бывает сценарий, когда дочерний компонент должен сообщить родителю, что он уже отобразился. Для этого после mounted удобно эмитить событие.
<!-- Child.vue -->
<template>
<div>Дочерний компонент</div>
</template>
<script>
export default {
name: 'ChildComponent',
mounted() {
// Сообщаем родителю, что компонент смонтирован
this.$emit('mounted')
}
}
</script>
<!-- Parent.vue -->
<template>
<ChildComponent @mounted="onChildMounted" />
</template>
<script>
import ChildComponent from './Child.vue'
export default {
components: { ChildComponent },
methods: {
onChildMounted() {
// Здесь родитель узнает, что дочерний компонент отображен
console.log('Дочерний компонент смонтирован')
}
}
}
</script>
Теперь вы увидите, как это выглядит в коде: ребенок отправляет событие в mounted, родитель его ловит.
Отличия mounted от других хуков
mounted vs created
Эти два хука часто путают. Давайте посмотрим, в чем разница.
created:
- вызывается после инициализации данных и методов;
- DOM еще не создан, this.$el и refs недоступны;
- хорошо подходит для:
- инициализации состояния;
- выполнения запросов, не зависящих от DOM;
- настройки таймеров, реактивных наблюдателей.
mounted:
- вызывается после того, как DOM уже вставлен в документ;
- refs и this.$el доступны;
- подходит для:
- работы с DOM;
- инициализации виджетов;
- измерения элементов;
- "ленивых" запросов, завязанных на отображение.
Чтобы разница была нагляднее, давайте посмотрим пример с комментариями.
export default {
data() {
return {
serverConfig: null,
}
},
async created() {
// В created загружаем конфигурацию, не зависящую от DOM
this.serverConfig = await fetch('/api/config').then(r => r.json())
// Здесь DOM еще нет - this.$el недоступен
},
mounted() {
// В mounted можем использовать конфигурацию и DOM вместе
console.log('Конфигурация', this.serverConfig)
console.log('Корневой элемент компонента', this.$el)
}
}
mounted vs updated
Еще одно противопоставление — mounted и updated.
- mounted вызывается один раз, при первом монтировании.
- updated вызывается при каждом обновлении, когда реактивные данные изменились и DOM перерисован.
Использовать updated стоит аккуратно, потому что:
- он может вызываться часто;
- неосторожные изменения в updated могут вызывать бесконечные циклы обновления.
Часто в updated применяют те же задачи, что и в mounted, но уже относительно обновленного DOM. Например, пересчет размеров после изменения списка.
export default {
data() {
return {
items: []
}
},
mounted() {
// Первичное измерение после монтирования
this.measure()
},
updated() {
// Перемеряем после каждого обновления списка
this.measure()
},
methods: {
measure() {
// Здесь мог бы быть доступ к this.$refs или this.$el
// для измерения размеров элемента
console.log('Пересчитываем размеры после изменений')
}
}
}
Типичные ошибки при использовании mounted и как их избежать
Хранение бизнес‑логики в mounted
Частая ошибка — класть в mounted бизнес‑логику, которая никак не связана с DOM или фактом отображения.
Например:
- в mounted выполняют все запросы к API;
- в mounted инициализируют глобальные хранилища;
- в mounted запускают сложные вычисления.
Проблема: такой код усложняет понимание и тестирование, а также может замедлить первый рендер.
Рекомендация:
- все, что не зависит от DOM и отображения, выносить в created / setup или в отдельные модули;
- оставить в mounted только то, что связано с:
- DOM;
- виджетами;
- событиями окна / документа;
- действиями, которые логично начинать именно после появления компонента.
Забытые обработчики и таймеры
Вторая распространенная проблема — добавление обработчиков в mounted без их удаления при размонтировании.
Например:
export default {
mounted() {
// Добавили обработчик
window.addEventListener('resize', this.onResize)
// Запустили таймер
this.intervalId = setInterval(this.tick, 1000)
}
}
Если не удалить обработчик и не очистить интервал в beforeUnmount, то:
- обработчики будут продолжать вызываться даже после уничтожения компонента;
- ссылки на компонент задержатся в памяти, что приведет к утечкам.
Правильный вариант:
export default {
mounted() {
// Настраиваем обработчики и таймеры
window.addEventListener('resize', this.onResize)
this.intervalId = setInterval(this.tick, 1000)
},
beforeUnmount() {
// Снимаем обработчик
window.removeEventListener('resize', this.onResize)
// Очищаем таймер
clearInterval(this.intervalId)
},
methods: {
onResize() {
// Обработка изменения размера
},
tick() {
// Периодическое действие
}
}
}
Асинхронность и изменение состояния после размонтирования
Еще один нюанс — асинхронный код, запущенный в mounted. Представьте, что:
- вы запустили запрос в mounted;
- пользователь быстро переключился на другую страницу;
- компонент размонтировался;
- но запрос завершился и пытается обновить состояние.
Это может вызывать предупреждения или ошибки. Один из подходов — добавлять флаг активности.
export default {
data() {
return {
isActive: true, // Флаг актуальности компонента
data: null,
}
},
async mounted() {
try {
const response = await fetch('/api/data')
const result = await response.json()
// Проверяем, что компонент еще не размонтирован
if (!this.isActive) return
this.data = result
} catch (e) {
if (!this.isActive) return
// Обрабатываем ошибку только если компонент еще существует
console.error(e)
}
},
beforeUnmount() {
// Помечаем, что компонент больше не активен
this.isActive = false
}
}
Так вы защищаете компонент от обновлений после его уничтожения.
Практические паттерны использования mounted
Согласование размеров родителя и потомка
Иногда вам нужно, чтобы дочерний компонент подстроился под размеры родительского контейнера. Здесь mounted в дочернем компоненте помогает измерить размеры после вставки в DOM.
<!-- Parent.vue -->
<template>
<div ref="wrapper" class="wrapper">
<ChildComponent :width="width" :height="height" />
</div>
</template>
<script>
import ChildComponent from './Child.vue'
export default {
components: { ChildComponent },
data() {
return {
width: 0,
height: 0,
}
},
mounted() {
// Измеряем размеры родительского контейнера
const rect = this.$refs.wrapper.getBoundingClientRect()
this.width = rect.width
this.height = rect.height
}
}
</script>
<!-- Child.vue -->
<template>
<div class="child">
<!-- Используем размеры, полученные от родителя -->
Ширина {{ width }}, высота {{ height }}
</div>
</template>
<script>
export default {
props: {
width: Number,
height: Number,
}
}
</script>
Здесь mounted родителя выполняет измерения и передает результат дочернему компоненту через пропсы.
Отложенное отображение тяжелых блоков
Смотрите, я покажу вам подход, когда "тяжелый" компонент рендерится только после mounted, чтобы ускорить первый рендер.
<template>
<div>
<div>Основной контент</div>
<!-- Тяжелый компонент показываем только после mounted -->
<HeavyComponent v-if="showHeavy" />
</div>
</template>
<script>
import HeavyComponent from './HeavyComponent.vue'
export default {
components: { HeavyComponent },
data() {
return {
showHeavy: false,
}
},
mounted() {
// Отложенное включение тяжелого компонента
this.showHeavy = true
}
}
</script>
Пока mounted не сработал, HeavyComponent не рендерится вообще, и пользователь быстрее видит основной контент.
Мягкая анимация появления компонента
Еще один паттерн — подключить анимацию уже после того, как компонент появился в DOM.
<template>
<div
ref="box"
class="box"
:class="{ 'box--visible': visible }"
>
Блок с анимацией
</div>
</template>
<script>
export default {
data() {
return {
visible: false, // Флаг видимости с анимацией
}
},
mounted() {
// Даем браузеру отрисовать начальное состояние
requestAnimationFrame(() => {
// Включаем видимость с анимацией
this.visible = true
})
}
}
</script>
<style>
.box {
opacity: 0;
transition: opacity 0.3s ease;
}
.box--visible {
opacity: 1;
}
</style>
Здесь важно, что изменение флага visible происходит уже после первого кадра анимации, инициированного в mounted через requestAnimationFrame.
Заключение
Этап mounted — это момент, когда компонент уже полностью отображен в DOM и готов к работе с реальными элементами, размерами, позиционированием и сторонними библиотеками.
К этому моменту:
- доступны все реактивные данные, вычисляемые свойства и методы;
- можно безопасно использовать this.$el и refs;
- удобно инициализировать виджеты и глобальные обработчики.
Вместе с тем, чтобы код оставался понятным и поддерживаемым, стоит придерживаться нескольких принципов:
- размещать в mounted только то, что связано с DOM и фактом отображения;
- очищать обработчики и таймеры в beforeUnmount;
- аккуратно работать с асинхронным кодом и учитывать возможное размонтирование;
- не подменять mounted за все остальные этапы жизненного цикла.
Если вы будете рассматривать mounted как четко очерченный момент "компонент уже в DOM", а не "место, куда складываем все подряд", работа с ним станет предсказуемой и простой.
Частозадаваемые технические вопросы по теме и ответы
Как запустить код строго после того, как DOM обновился внутри mounted
Иногда нужно дождаться не только mounted, но и следующего обновления DOM, вызванного изменением данных. В Vue можно использовать метод nextTick.
Мини-инструкция:
- В mounted измените данные, как обычно.
- После этого вызовите this.$nextTick.
- В колбэке this.$nextTick работайте с уже обновленным DOM.
Пример:
mounted() {
this.items.push('новый')
this.$nextTick(() => {
// Здесь DOM уже учитывает новый элемент
const last = this.$refs.lastItem
// Дальнейшая работа с элементом
})
}
Как выполнить mounted только на клиенте при SSR
При серверном рендеринге (SSR) mounted вызывается только на клиенте, на сервере он не выполняется. Если внутри mounted есть код, зависящий от window или document, он уже безопасен. Главное — не использовать такие объекты вне mounted или других client-only хуков. Дополнительной настройки, как правило, не требуется.
Как правильно тестировать логику внутри mounted
При модульном тестировании удобно вынести логику из mounted в отдельный метод, а в самом mounted лишь вызывать его. В тесте вы можете:
- Смонтировать компонент с помощью тестовой библиотеки.
- Проверить, что метод, используемый в mounted, был вызван (через шпион/мок).
- Тестировать сам метод отдельно, передавая ему поддельные данные или DOM.
Это делает тесты проще и не привязывает их жестко к жизненному циклу.
Как использовать mounted в композиционном API (setup)
В Composition API вместо mounted используют onMounted. Мини-инструкция:
- Импортируйте onMounted из vue.
- Внутри setup вызовите onMounted и передайте колбэк.
- Внутри колбэка используйте ссылки (ref) на элементы и реактивные данные.
Пример:
import { ref, onMounted } from 'vue'
export default {
setup() {
const el = ref(null)
onMounted(() => {
// el.value — DOM-элемент
console.log(el.value)
})
return { el }
}
}
Как отследить момент монтирования нескольких динамических дочерних компонентов
Если у вас список дочерних компонентов по v-for, и вам нужно знать, когда все они смонтировались, используйте счетчик:
- В родителе заведите счетчик смонтированных детей.
- В каждом дочернем компоненте эмитьте событие в mounted.
- В родителе увеличивайте счетчик и сравнивайте его с количеством элементов в списке.
- Когда счетчик равен длине списка, можно считать, что все дочерние компоненты смонтированы.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

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