Олег Марков
Монтирование компонента - хук beforeMount в Vue
Введение
Монтирование компонента в Vue – это момент, когда декларативное описание интерфейса превращается в реальные DOM-элементы в браузере. На этом пути компонент проходит несколько этапов жизненного цикла, и один из них – хук beforeMount.
Здесь важно понимать, что beforeMount – не просто «еще один хук». Он строго привязан к конкретной точке во времени: когда виртуальный DOM уже построен, но еще не синхронизирован с реальным DOM. Это означает, что у вас есть доступ к данным и вычисляемым значениям, но вы еще не можете безопасно работать с DOM через ссылки ref.
В этой статье вы увидите:
- на каком этапе вызывается beforeMount и как он вписывается в общий жизненный цикл компонента Vue
- что именно доступно внутри beforeMount, а что еще недоступно
- чем beforeMount отличается от created и mounted на практике
- реальные примеры использования, где beforeMount действительно нужен
- распространенные ошибки и анти-паттерны, связанные с beforeMount
Давайте шаг за шагом разберемся, что это за хук и когда его действительно стоит использовать.
Монтирование компонента в Vue и место beforeMount
Жизненный цикл компонента в общих чертах
Для начала уточним общий порядок вызова основных хуков жизненного цикла при первом монтировании компонента (для Vue 2 и принципиально схоже для Vue 3 в Options API):
- beforeCreate
- created
- beforeMount
- mounted
При последующих обновлениях вызываются другие хуки (beforeUpdate, updated), а при удалении – beforeUnmount (или beforeDestroy в Vue 2) и unmounted (destroyed в Vue 2). В этой статье нас интересует именно участок между created и mounted.
Если описать кратко:
- created – данные уже инициализированы, но компонент еще никак не связан с DOM
- beforeMount – шаблон компонента уже преобразован во внутреннюю структуру (виртуальный DOM), но реальные DOM-узлы еще не созданы и не вставлены в документ
- mounted – реальный DOM уже создан и вставлен в страницу, можно безопасно работать с ref и размерами элементов
Где именно находится beforeMount
Смотрите, я покажу вам это на временной шкале. Условно можно представить процесс так (сильно упрощенно):
- Создание экземпляра компонента
- инициализация props
- инициализация data, computed, methods
- настройка реактивности
- beforeCreate
- created
- Подготовка к монтированию
- разбор шаблона
- построение виртуального DOM-представления
- beforeMount
- Создание реальных DOM-элементов из виртуального DOM
- Вставка DOM-дерева в документ
- mounted
Ключевой момент: beforeMount вызывается один раз, только при первом монтировании компонента. При повторных рендерах (когда меняются реактивные данные) beforeMount уже не вызывается.
Отличие поведения в Vue 2 и Vue 3
Важно разделять две ситуации:
- new Vue({ el: '#app' }) в Vue 2 – монтирование происходит автоматически, без явного вызова $mount
- createApp(App).mount('#app') в Vue 3 – также есть явное монтирование, но API немного отличается
Тем не менее смысл beforeMount остается одинаковым:
- он вызывается один раз перед первым отражением виртуального DOM в реальном DOM
- структура шаблона уже рассчитана, но реальные DOM-элементы еще не созданы
С точки зрения обычного разработчика, работающего с Options API, вы можете считать поведение одинаковым в обоих случаях.
Что доступно и что недоступно в beforeMount
Давайте четко разделим, с чем вы можете работать внутри beforeMount, а с чем – нет.
Что уже доступно в beforeMount
К моменту вызова beforeMount:
- инициализированы props – вы можете читать значения, переданные родителем
- инициализированы data – все поля data доступны и реактивны
- готовы methods – вы можете вызывать методы компонента
- готовы computed – вычисляемые свойства уже работают
- доступны watch – наблюдатели уже могут реагировать на изменения
- this указывает на экземпляр компонента и ведет себя так же, как в created или mounted
Пример:
export default {
data() {
return {
message: 'Привет',
ready: false,
}
},
computed: {
upperMessage() {
// Здесь мы возвращаем строку в верхнем регистре
return this.message.toUpperCase()
},
},
beforeMount() {
// В beforeMount мы уже можем читать данные и computed
console.log('message в beforeMount:', this.message) // 'Привет'
console.log('upperMessage в beforeMount:', this.upperMessage) // 'ПРИВЕТ'
// Можно изменять состояние
this.ready = true
},
}
Как видите, с точки зрения данных beforeMount ничем не отличается от created или mounted.
Чего еще нет в beforeMount
Самое важное ограничение: в beforeMount еще нет реального DOM. А значит:
- ссылки ref на элементы шаблона еще не установлены
- нельзя получить размеры элементов, их позицию и т. п.
- любые прямые манипуляции с DOM через this.$refs или document.querySelector по элементам компонента могут работать некорректно или непредсказуемо
Посмотрим пример того, что делать не стоит:
export default {
template: `
<div>
<input ref="input" />
</div>
`,
beforeMount() {
// Плохой пример - ref еще не установлен
console.log(this.$refs.input) // Скорее всего undefined
},
mounted() {
// Правильное место для работы с ref
console.log(this.$refs.input) // Здесь уже будет реальный DOM-элемент <input>
},
}
Здесь я специально показываю плохой пример в beforeMount, чтобы вам было проще запомнить: не рассчитывайте на this.$refs в beforeMount. Для этого есть mounted.
Важный нюанс с серверным рендерингом (SSR)
Если вы работаете с SSR (Nuxt или Vue SSR), стоит понимать:
- хуки, связанные с DOM (beforeMount, mounted и далее), на сервере не вызываются
- на сервере выполняются только хуки до монтирования DOM (beforeCreate, created)
Однако при гидратации (когда уже отрендеренный HTML «привязывается» к Vue на клиенте) beforeMount будет вызван уже в браузере перед тем, как Vue «вживит» свою реактивную систему в готовую разметку.
Это еще один аргумент в пользу правила: все, что зависит от DOM или окружения браузера (window, document, элементы страницы), располагайте в mounted, а не в beforeMount.
Сравнение beforeMount с created и mounted
Чтобы уверенно использовать beforeMount, удобно сравнить его с соседними по жизненному циклу хуками.
Сравнение с created
Сначала давайте разберемся с created.
Хук created вызывается:
- после инициализации реактивных данных, props, computed, methods
- до начала процесса монтирования (до построения виртуального DOM)
beforeMount вызывается позже, когда шаблон уже разобран и виртуальный DOM готов.
Но для вас, как разработчика, есть более практичные отличия:
- created — идеален для:
- инициализации данных, не зависящих от DOM
- начальных запросов к API
- настройки таймеров, подписок, реактивных наблюдателей
- beforeMount — практически не дает новых возможностей по сравнению с created, если вы не углубляетесь во внутренности Vue
Часто можно встретить рекомендацию: «если вы не пишете низкоуровневые вещи или плагины, beforeMount вам почти не нужен». В большинстве случаев created будет достаточен.
Пример, где хватит created:
export default {
data() {
return {
items: [],
isLoading: true,
}
},
async created() {
// Здесь мы выполняем загрузку данных до монтирования
// и это логичнее делать именно в created
try {
// Отправляем HTTP-запрос за данными
const response = await fetch('/api/items')
const data = await response.json()
this.items = data
} finally {
// Завершаем состояние загрузки
this.isLoading = false
}
},
}
Переносить подобную логику в beforeMount нет смысла – вы ничего не выигрываете.
Сравнение с mounted
Вот тут отличие заметнее и гораздо более практично.
- beforeMount – виртуальный DOM уже есть, но реального еще нет
- mounted – реальный DOM уже создан, вставлен в страницу, refs доступны
Поэтому все, что зависит от реального DOM, имеет смысл делать только в mounted:
- доступ к this.$refs
- измерение размеров элементов (offsetWidth, getBoundingClientRect и т. п.)
- инициализация сторонних библиотек, которые ожидают существующий DOM (например, слайдеры, карты, сторонние UI-компоненты)
Давайте посмотрим на пример с использованием библиотеки, которая рисует график на canvas:
export default {
template: `
<canvas ref="chartCanvas"></canvas>
`,
data() {
return {
chartInstance: null,
chartData: [10, 20, 30],
}
},
beforeMount() {
// Здесь мы можем подготовить данные, но не создавать график
// потому что DOM-элемент canvas еще не существует
console.log('Данные для графика готовы:', this.chartData)
},
mounted() {
// Теперь DOM уже есть - можно инициализировать график
const canvas = this.$refs.chartCanvas
// chartLibrary - условная внешняя библиотека
this.chartInstance = chartLibrary.createChart(canvas, {
// Здесь мы передаем данные для отрисовки графика
data: this.chartData,
})
},
}
Здесь видно четкое разделение ролей:
- beforeMount – максимальная подготовка данных и состояния, не зависящих от DOM
- mounted – финальная инициализация, которая требует реального DOM
Практическое применение beforeMount
Теперь давайте разберем ситуации, где beforeMount действительно может оказаться полезен и отличается от created и mounted.
1. Логирование и отладка процесса монтирования
Как ни странно, одна из частых практических задач – отладка порядка вызова хуков и понимание, когда что происходит. Для этого beforeMount очень удобен.
Пример:
export default {
data() {
return {
stage: 'init',
}
},
beforeCreate() {
console.log('beforeCreate - данные еще не инициализированы')
},
created() {
console.log('created - данные уже есть')
console.log('stage в created:', this.stage) // 'init'
},
beforeMount() {
// Здесь мы можем понять - монтирование вот-вот начнется
console.log('beforeMount - виртуальный DOM готов')
this.stage = 'about-to-mount'
},
mounted() {
console.log('mounted - компонент уже в DOM')
console.log('stage в mounted:', this.stage) // 'about-to-mount'
},
}
Такой «дневник» жизненного цикла помогает лучше понять момент вызова beforeMount и то, что в нем уже можно обращаться к данным.
2. Логика, завязанная на различие сервер / клиент при SSR
Если вы работаете с SSR, created может отработать и на сервере, и на клиенте. Иногда вам нужно выполнить какую-то логику только на клиенте, но до того, как компонент будет смонтирован.
Например, вы хотите:
- до монтирования проверить локальное хранилище (localStorage)
- подготовить какие-то клиентские данные
- не трогать DOM, но уже использовать возможности браузера
В такой ситуации beforeMount – удобная точка входа: он точно не отработает на сервере.
Пример:
export default {
data() {
return {
theme: 'light',
}
},
created() {
// В SSR этот хук может выполниться на сервере
// поэтому здесь мы не используем localStorage
},
beforeMount() {
// Этот хук гарантированно выполняется только в браузере
// поэтому здесь мы можем работать с localStorage
const savedTheme = window.localStorage.getItem('theme')
if (savedTheme === 'dark' || savedTheme === 'light') {
// Устанавливаем тему из хранилища, если она сохранена
this.theme = savedTheme
}
},
}
Смотрите, я показываю этот пример, чтобы подчеркнуть: если вы явно разделяете серверную и клиентскую логику, beforeMount – удобная граница.
3. Предварительная подготовка состояния перед доступом к DOM
Иногда нужно гарантировать, что к моменту mounted у компонента уже выставлено определенное состояние, с которым вы будете работать при первом доступе к DOM.
Да, это можно сделать и в created, но иногда вам важно выполнить это как можно ближе к моменту монтирования. Например:
- вы подбираете тему оформления на основе системных настроек
- вы устанавливаете флаг, который должен отразиться в шаблоне при первом рендере
Пример:
export default {
data() {
return {
prefersReducedMotion: false,
animationsEnabled: true,
}
},
created() {
// Здесь мы могли бы выполнить запросы к API,
// но настройки окружения лучше проверять ближе к клиенту
},
beforeMount() {
// Проверяем системную настройку "уменьшение движения"
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
// Сохраняем результат в состояние компонента
this.prefersReducedMotion = mediaQuery.matches
// Если пользователь предпочитает уменьшенное движение - отключаем анимации
if (this.prefersReducedMotion) {
this.animationsEnabled = false
}
},
mounted() {
// Здесь мы уже можем читать animationsEnabled
// и инициализировать анимации c учетом пользовательских предпочтений
if (this.animationsEnabled) {
// initAnimations - условная функция инициализации анимаций
initAnimations()
}
},
}
Здесь важно, что к моменту первого реального рендера (создания DOM) шаблон уже знает, что анимации нужно отключить, и не будет пытаться их включать.
4. Интеграция с глобальными системами, не трогающими DOM
Еще одна менее очевидная, но полезная категория задач – интеграция с глобальными, но не DOM-зависимыми объектами:
- логгеры событий жизненного цикла
- счетчики аналитики (которые не требуют DOM)
- глобальные системы состояния вне Vue (например, отдельный store, не Vuex/Pinia)
Например, вы хотите отправить событие аналитики «экран почти отрисован», но еще до фактического появления в DOM:
import analytics from '@/services/analytics'
export default {
name: 'ProductPage',
beforeMount() {
// Отправляем событие в аналитику, что страница почти готова
analytics.track('product_page_about_to_mount', {
// Здесь мы передаем тип события в аналитику
component: this.$options.name,
timestamp: Date.now(),
})
},
mounted() {
// В mounted вы уже можете отправить другое событие
// что страница действительно отрисована пользователю
analytics.track('product_page_mounted', {
component: this.$options.name,
timestamp: Date.now(),
})
},
}
Разделение этих событий помогает точнее измерять время от «данные готовы» до «страница реально видна».
Типичные ошибки и анти-паттерны с beforeMount
Теперь давайте посмотрим, какие вещи в beforeMount делать не стоит.
Ошибка 1. Попытка работать с DOM или ref
Это самая частая проблема. Разработчики думают: «beforeMount – значит DOM уже почти есть, можно его трогать». Но нет.
Пример анти-паттерна:
export default {
template: `
<div ref="box"></div>
`,
beforeMount() {
// Пытаемся измерить размеры элемента до монтирования
// Это не сработает - box еще не создан
const width = this.$refs.box?.offsetWidth
console.log('Ширина коробки в beforeMount:', width) // Скорее всего undefined
},
mounted() {
// Правильное место для измерения размеров
const width = this.$refs.box.offsetWidth
console.log('Ширина коробки в mounted:', width) // Здесь уже будет число
},
}
Правило можно запомнить так: if this.$refs нужен – используйте mounted, не beforeMount.
Ошибка 2. Ожидание, что beforeMount будет вызываться при каждом обновлении
Иногда разработчики ожидают, что beforeMount как-то участвует в последующих обновлениях компонента, но он вызывается только один раз, при первом монтировании.
Если вам нужно выполнять логику перед каждым обновлением DOM:
- используйте beforeUpdate
- или используйте watch для отслеживания конкретных данных
Пример неправильного ожидания:
export default {
data() {
return {
count: 0,
}
},
beforeMount() {
// Некоторые разработчики ошибочно думают,
// что этот код будет выполняться при каждом изменении count
console.log('beforeMount - count:', this.count)
},
methods: {
increment() {
// Увеличиваем счетчик на 1
this.count++
},
},
}
Этот console.log отработает только один раз. Если вам нужно реагировать на изменения count, сделайте так:
export default {
data() {
return {
count: 0,
}
},
watch: {
count(newValue, oldValue) {
// Этот код выполнится при каждом изменении count
console.log('count изменился с', oldValue, 'на', newValue)
},
},
methods: {
increment() {
// Увеличиваем счетчик и тем самым запускаем watch
this.count++
},
},
}
Ошибка 3. Злоупотребление beforeMount вместо created
Бывает, что разработчик по привычке помещает всю «начальную» логику в beforeMount, хотя она спокойно могла бы находиться в created.
Типичные примеры:
- запросы к API, не зависящие от DOM
- установка начальных значений data
- инициализация локальных структур данных
Пример неудачного использования:
export default {
data() {
return {
users: [],
}
},
beforeMount() {
// Это лучше поместить в created, а не в beforeMount
this.loadUsers()
},
methods: {
async loadUsers() {
// Загружаем список пользователей с сервера
const response = await fetch('/api/users')
this.users = await response.json()
},
},
}
Более естественно и логично сделать так:
export default {
data() {
return {
users: [],
}
},
created() {
// created лучше подходит для загрузки данных
this.loadUsers()
},
methods: {
async loadUsers() {
// Загружаем список пользователей с сервера
const response = await fetch('/api/users')
this.users = await response.json()
},
},
}
Ошибка 4. Логика, которая должна выполняться и при повторном монтировании
Если компонент может многократно добавляться и удаляться (например, внутри условного рендеринга v-if), то хуки монтирования будут вызываться при каждом новом добавлении:
- beforeMount – перед каждым новым созданием DOM для этого экземпляра
- mounted – после каждого добавления DOM в документ
Иногда разработчик ожидает, что какая-то логика выполнится и при обновлениях, и при повторном монтировании, и помещает ее только в beforeMount.
В такой ситуации может оказаться, что корректнее было бы использовать комбинацию:
- created – для одноразовой инициализации экземпляра
- beforeMount – если нужно что-то делать именно перед каждым новым монтированием
- mounted – если есть DOM-зависимая часть
Важно не забыть: если элемент удаляется из DOM, но экземпляр компонента при этом не уничтожается (например, при v-show), beforeMount и mounted не вызываются снова, так как экземпляр считается все еще смонтированным. В таком случае beforeMount тем более не поможет вам как «универсальная точка» логики.
beforeMount в Composition API
Если вы работаете с Composition API (Vue 3), вам может показаться, что хуки меняются. На самом деле меняется только форма записи.
Вместо отдельных опций beforeMount, mounted и т. д. вы используете функции-хуки внутри setup:
- onBeforeMount – аналог beforeMount
- onMounted – аналог mounted
Смотрите, как это выглядит в коде:
import { ref, onBeforeMount, onMounted } from 'vue'
export default {
setup() {
const message = ref('Привет из setup')
const isReady = ref(false)
onBeforeMount(() => {
// Здесь мы можем работать с реактивными переменными
console.log('onBeforeMount - message:', message.value)
isReady.value = true
})
onMounted(() => {
// Здесь безопасно работать с DOM и refs
console.log('onMounted - компонент уже в DOM')
})
// Возвращаем значения, которые будут доступны в шаблоне
return {
message,
isReady,
}
},
}
Смысл полностью совпадает с Options API:
- onBeforeMount – данные уже есть, DOM еще нет
- onMounted – DOM уже есть, refs доступны
Поведение хуков жизненного цикла сохраняется, меняется лишь синтаксис.
Краткие рекомендации по использованию beforeMount
Давайте обобщим, когда стоит применять beforeMount, а когда – нет.
Когда beforeMount использовать уместно
- вы работаете с SSR и хотите выполнить код только на клиенте, но до монтирования DOM
- вам нужно логировать или анализировать этапы жизненного цикла компонента
- вы хотите подготовить состояние максимально поздно, но до реального рендера
- вы пишете низкоуровневую интеграцию или плагин, завязанный на внутренности Vue (редкий, но возможный сценарий)
Когда лучше обойтись без beforeMount
- для загрузки данных с сервера – используйте created
- для работы с DOM, refs, размерами элементов – используйте mounted
- для обработки изменений данных – используйте watch или хуки обновления (beforeUpdate, updated)
- если вы просто ищете «место, куда положить инициализацию» – чаще всего это created или mounted
Простое правило выбора хука
Если обобщить в одну короткую схему:
- Нужно выполнить логику один раз при создании данных – created
- Нужно выполнить что-то только в браузере и до появления DOM – beforeMount
- Нужно выполнить что-то после появления реального DOM – mounted
Так вы избежите большинства типичных ошибок и не будете использовать beforeMount там, где он не дает никаких преимуществ.
Заключение
beforeMount – это хук жизненного цикла Vue, который вызывается в очень узкий и специфический момент: после создания виртуального DOM, но до синхронизации с реальным DOM. К этому моменту у вас уже полностью инициализированы данные, методы и вычисляемые свойства, но ссылки на DOM-элементы еще недоступны.
В реальных приложениях beforeMount используется не так часто, как created или mounted. Чаще всего он полезен:
- при работе с SSR для логики, которая должна выполняться только в браузере
- при тонкой настройке поведения на стыке «данные готовы – DOM вот-вот появится»
- при отладке и логировании жизненного цикла компонента
Главное – не пытаться использовать beforeMount для задач, которые по смыслу относятся к другим этапам: загрузка данных больше подходит created, работа с DOM – mounted, а реакция на изменения данных – watch и хуки обновления.
Если вы будете четко разделять ответственность разных хуков и помнить, что beforeMount не имеет доступа к реальному DOM, ошибок станет заметно меньше, а код – понятнее и предсказуемее.
Частозадаваемые технические вопросы по теме и ответы
Как в beforeMount получить доступ к элементу DOM если очень нужно
Никак корректно. В beforeMount реального DOM еще нет, поэтому доступ к элементам невозможен. Если вам «очень нужно», это сигнал, что вы выбрали неправильный хук. Перенесите логику в mounted. Если же требуется именно «подготовка перед DOM», разделите логику на два этапа: подготовка данных в beforeMount или created, а работа с DOM в mounted.
Можно ли в beforeMount вызывать this.$nextTick чтобы дождаться DOM
Да, но это бессмысленно. this.$nextTick планирует колбэк после обновления DOM, однако сам хук beforeMount вызывается до первой записи в DOM. Правильнее поместить DOM-зависимый код в mounted. Там при необходимости уже можно использовать this.$nextTick для ожидания следующего цикла обновления.
Вызывается ли beforeMount у дочерних компонентов до или после mounted родителя
Порядок такой: сначала вызываются beforeMount у родительского и всех дочерних компонентов (в глубину дерева), затем создается DOM, и уже после этого вызываются mounted начиная с самого глубокого дочернего компонента к родителю. То есть beforeMount родителя и детей вызываются до любого mounted.
Срабатывает ли beforeMount повторно если компонент скрывается и показывается через v-show
Нет. При v-show компонент не демонтируется, а только меняет CSS-свойство display. Хуки монтирования (beforeMount, mounted) вызываются один раз при первом добавлении компонента. Для логики, связанной с показом и скрытием при v-show, используйте watch на значение, управляющее видимостью, либо вычисляемые свойства.
Как лучше тестировать логику внутри beforeMount в юнит-тестах
В большинстве тестовых утилит (например Vue Test Utils) вы можете смонтировать компонент через mount или shallowMount. При этом хуки created, beforeMount и mounted будут выполнены. Если вы хотите проверить только beforeMount без реального DOM, используйте shallowMount (он не рендерит дочерние компоненты), а в тесте проверяйте изменения состояния, которые должны произойти в beforeMount. DOM-зависимый код в beforeMount лучше рефакторить и выносить в mounted или отдельные функции.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

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