Олег Марков
Инициализация данных в состоянии created - как и когда подготавливать данные в приложении
Введение
Инициализация данных в состоянии created (или аналогичных ранних хуках жизненного цикла) — это момент, когда вы подготавливаете «внутренний мир» приложения до того, как пользователь что‑то увидит на экране. На этом шаге вы:
- задаете начальные значения полей;
- запускаете асинхронные запросы;
- приводите приходящие данные к удобному для приложения формату;
- настраиваете локальное состояние, зависящее от входных параметров (например, URL или props).
Смотрите, я покажу вам, как этот этап помогает разделить логику «подготовки данных» и «работы с DOM», и почему ошибка выбора неправильного момента инициализации часто приводит к неясным багам — от пустых списков до мигающих интерфейсов.
Давайте опираться в терминах на типичную модель фронтенд‑фреймворков с жизненным циклом компонента (на примере Vue и близких по идее подходов), где есть ранний хук created, а затем более поздние, уже связанные с DOM, вроде mounted.
Что такое инициализация данных в состоянии created
Основная идея
Когда говорят «инициализация данных — created», обычно имеют в виду:
- момент времени, когда компонент уже:
- создан как объект;
- получил входные данные (props или параметры);
- имеет реактивное состояние (data, state);
- но еще:
- не привязан к DOM;
- не отрисован в интерфейсе;
- не имеет доступа к элементам разметки.
В этот период удобно:
- задать начальные значения полей модели;
- дернуть API и сохранить результат;
- подготовить вычисляемые структуры (кэш, словари, индексы);
- синхронизировать состояние с внешними параметрами (route, query, локальное хранилище).
Почему именно created, а не позже
Обратите внимание: если вы инициализируете данные слишком поздно — уже после появления компонента на экране — пользователь успеет увидеть:
- пустые поля;
- мигающую верстку;
- быструю смену «заглушки» на реальные данные.
Ранняя инициализация в created позволяет:
- загрузить и подготовить данные до того, как они попадут в шаблон;
- минимизировать количество лишних перерасчетов и перерисовок;
- сделать поведение компонента более предсказуемым.
Ключевые задачи фазы created
В типичном приложении в created вы:
- Читаете входные параметры (props, query‑параметры, состояние router).
- На их основе формируете начальный state.
- Запускаете асинхронные операции:
- загрузка сущности по id;
- запрос конфигурации;
- проверка сессии, токена, прав доступа.
- Нормализуете и приводите данные к внутреннему формату.
Типовой жизненный цикл и место created
Сопоставление с другими стадиями
Давайте разберемся на упрощенной последовательности этапов жизни компонента:
- Конструирование:
- создание объекта компонента;
- чтение опций (data, computed, методы).
- Инициализация реактивности:
- поля становятся «наблюдаемыми»;
- прослушиваются изменения.
- Хук
created:- доступно все реактивное состояние;
- доступны входные данные;
- нет еще DOM.
- Подготовка DOM и привязка шаблона.
- Хук
mounted:- DOM уже создан и привязан;
- можно работать с элементами, измерениями, сторонними библиотеками.
- Дальнейшие обновления, хуки обновления и уничтожения.
В контексте инициализации данных нас интересует именно шаг 3: в этот момент мы можем:
- безопасно записывать значения в state;
- запускать запросы;
- создавать структуры данных, от которых будут зависеть позже вычисляемые значения.
Какие данные инициализировать в created
Начальные значения состояния
Первое, что обычно делают в created, — задают или уточняют стартовое состояние.
Предположим, у вас есть компонент редактирования профиля. Часть значений вы уже указали в data, но часть хотите подстроить под текущего пользователя и параметры URL.
Смотрите, пример псевдокода в стиле Vue:
export default {
data() {
return {
profile: null, // Здесь будет объект профиля
loading: true, // Флаг загрузки
error: null, // Текст ошибки
readOnly: false, // Признак режима "только чтение"
}
},
created() {
// Здесь мы настраиваем readOnly в зависимости от параметров
const { mode } = this.$route.query // Берем параметр из URL
// Если mode == "view", включаем только чтение
this.readOnly = mode === 'view'
// Далее запускаем загрузку профиля
this.fetchProfile()
},
methods: {
async fetchProfile() {
try {
// Здесь мы обращаемся к API
const result = await api.getProfile()
// Сохраняем результат в реактивное поле
this.profile = result
} catch (err) {
// В случае ошибки сохраняем текст ошибки
this.error = err.message || 'Ошибка загрузки профиля'
} finally {
// В любом случае снимаем флаг загрузки
this.loading = false
}
}
}
}
Как видите, этот код:
- использует входные данные (query‑параметр);
- формирует режим работы (readOnly);
- запускает загрузку, которая завершит инициализацию профиля.
Загрузка и нормализация внешних данных
Часто на этапе created вы не просто грузите данные, но и приводите их к удобному формату.
Например, API возвращает такую структуру:
{
"id": 42,
"user_name": "john",
"roles": ["admin", "editor"],
"created_at": "2024-01-10T10:00:00Z"
}
Но в приложении вам удобнее держать:
- имена в camelCase;
- дату как объект Date;
- дополнительно вычисленный флаг isAdmin.
Теперь вы увидите, как это выглядит в коде:
export default {
data() {
return {
user: null, // Здесь будет уже нормализованный объект пользователя
loading: true,
error: null,
}
},
async created() {
// Обратите внимание - created может быть async
try {
const raw = await api.getCurrentUser() // Сырые данные с сервера
// Нормализация данных
this.user = {
id: raw.id,
userName: raw.user_name, // Перевод в camelCase
roles: raw.roles,
createdAt: new Date(raw.created_at), // Парсинг даты
isAdmin: raw.roles.includes('admin'), // Вычисляемый флаг
}
} catch (err) {
this.error = 'Не удалось получить данные пользователя'
console.error(err)
} finally {
this.loading = false
}
}
}
Такой подход делает шаблоны проще: вы не размазываете преобразования по всему коду, а концентрируете их в инициализации.
Практические сценарии инициализации в created
Инициализация в зависимости от props
Компонент может получать props (например, id сущности), и ваша задача — по ним инициализировать локальное состояние.
Давайте разберемся на примере:
export default {
props: {
articleId: {
type: Number,
required: true, // Компонент не имеет смысла без этого значения
}
},
data() {
return {
article: null, // Сюда загрузится статья
comments: [], // Здесь будут комментарии
loading: true,
error: null,
}
},
created() {
// Как только компонент создан - начинаем загрузку
// Используем входной параметр articleId
this.loadArticleWithComments(this.articleId)
},
methods: {
async loadArticleWithComments(id) {
this.loading = true
try {
// Здесь мы параллельно загружаем статью и комментарии
const [article, comments] = await Promise.all([
api.getArticle(id),
api.getComments(id),
])
this.article = article
this.comments = comments
} catch (err) {
this.error = 'Не удалось загрузить статью'
console.error(err)
} finally {
this.loading = false
}
}
}
}
Обратите внимание, как этот фрагмент кода решает задачу:
- компонент сам по себе не знает, какую статью показывать;
- props articleId приходит снаружи;
- created использует это значение, чтобы инициализировать внутреннее состояние.
Инициализация по состоянию маршрута
Во многих SPA‑приложениях компонент зависит от URL. Например, вы хотите:
- при заходе на
/users?tab=rolesоткрыть конкретную вкладку; - или отфильтровать список по параметрам поиска.
Покажу вам, как это реализовано на практике:
export default {
data() {
return {
activeTab: 'info', // Текущая активная вкладка
filter: '', // Строка поиска
}
},
created() {
// Здесь мы читаем параметры маршрута
const { tab, q } = this.$route.query
// Если есть параметр tab - используем его
if (tab) {
this.activeTab = tab
}
// Если есть строка поиска - подставляем
if (q) {
this.filter = q
}
}
}
Так вы синхронизируете состояние компонента с адресной строкой сразу на этапе создания.
Предзаполнение форм
Еще один типичный сценарий — предзаполнить форму исходными значениями:
- при редактировании сущности;
- при повторном открытии формы;
- при восстановлении черновика.
Давайте посмотрим, что происходит в следующем примере:
export default {
data() {
return {
form: {
name: '',
email: '',
newsletter: false,
},
loadingDraft: true,
}
},
async created() {
// Пытаемся загрузить черновик формы из локального хранилища
// или с сервера
try {
const draft = await api.loadDraft()
if (draft) {
// Если черновик найден - подставляем его в форму
this.form = {
name: draft.name || '',
email: draft.email || '',
newsletter: Boolean(draft.newsletter),
}
}
} finally {
// Важно - независимо от результата снимаем флаг загрузки
this.loadingDraft = false
}
}
}
Здесь я размещаю пример, чтобы вам было проще понять: created — удобное место для восстановления предыдущего состояния перед отображением формы.
Отличие инициализации в created от инициализации в mounted
Когда лучше created
Используйте created для инициализации данных, когда:
- вам не нужен доступ к DOM;
- вы хотите подготовить состояние до появления интерфейса;
- важно минимизировать мигание и лишние перерисовки;
- вы настраиваете внутреннюю модель по входным параметрам.
Типичные операции:
- загрузка данных с сервера;
- чтение и разбор query‑параметров;
- подстройка начальных фильтров и сортировок;
- нормализация приходящих структур.
Когда лучше mounted
Используйте mounted, если:
- вам нужно работать с реальным DOM:
- измерить размеры блока;
- подключить стороннюю библиотеку (например, плагин таблицы);
- навесить нестандартные обработчики событий;
- логика зависит от того, что компонент уже «физически» отрисован.
Инициализацию данных в mounted обычно делают только в редких случаях, например:
- если данные зависят от размеров окна или блока;
- если вы хотите сначала отрисовать skeleton, а потом загружать содержимое.
Асинхронная инициализация данных в created
Подход с async/await
Современные фреймворки и окружения позволяют писать created как async‑функцию. Это делает код проще и понятнее.
Теперь вы увидите, как это выглядит:
export default {
data() {
return {
items: [], // Список элементов
loading: false, // Признак загрузки
error: null, // Сообщение об ошибке
}
},
async created() {
// Включаем флаг загрузки
this.loading = true
try {
// Здесь мы выполняем асинхронный запрос
const data = await api.fetchItems()
// Сохраняем результат в реактивном состоянии
this.items = data
} catch (e) {
// В случае ошибки сохраняем ее текст
this.error = e.message || 'Ошибка получения списка'
} finally {
// В любом случае снимаем флаг загрузки
this.loading = false
}
}
}
Такой код легко читать и сопровождать, потому что:
- логика инициализации сосредоточена в одном месте;
- последовательность действий очевидна;
- обработка ошибок и финализация (finally) не размазаны.
Параллельная загрузка нескольких источников
Иногда вам нужно загрузить несколько сущностей сразу. В created удобно запускать их параллельно, чтобы сэкономить время.
Давайте разберемся на примере:
export default {
data() {
return {
user: null, // Данные пользователя
settings: null, // Настройки приложения
permissions: [], // Права доступа
loading: true,
error: null,
}
},
async created() {
try {
// Здесь мы запускаем три запроса параллельно
const [user, settings, permissions] = await Promise.all([
api.getUser(),
api.getSettings(),
api.getPermissions(),
])
// Сохраняем результаты
this.user = user
this.settings = settings
this.permissions = permissions
} catch (err) {
this.error = 'Не удалось инициализировать приложение'
console.error(err)
} finally {
this.loading = false
}
}
}
Преимущество такого подхода:
- все данные инициализируются за одно сетевое «окно»;
- пользователь быстрее получает готовый экран.
Организация кода инициализации
Вынесение логики в отдельные методы
Чтобы created не превратился в длинный «простыню» кода, удобно выносить части логики в методы.
Смотрите, я покажу вам, как это работает:
export default {
data() {
return {
profile: null,
notifications: [],
loading: true,
error: null,
}
},
async created() {
// Здесь мы просто вызываем один метод
await this.initializePage()
},
methods: {
async initializePage() {
this.loading = true
this.error = null
try {
// Разбираем инициализацию на смысловые блоки
await Promise.all([
this.loadProfile(),
this.loadNotifications(),
])
} catch (err) {
this.error = 'Не удалось инициализировать страницу'
console.error(err)
} finally {
this.loading = false
}
},
async loadProfile() {
// Загружаем профиль
this.profile = await api.getProfile()
},
async loadNotifications() {
// Загружаем уведомления
this.notifications = await api.getNotifications()
}
}
}
Такой подход:
- делает created максимально читаемым;
- облегчает тестирование отдельных частей инициализации;
- позволяет переиспользовать методы из других мест (например, при ручном обновлении данных).
Повторная инициализация (re‑init)
Иногда компонент должен уметь «переинициализироваться» при изменении props или маршрута. В этом случае часть кода, который вы обычно писали в created, имеет смысл вынести в отдельный метод и вызывать его:
- из created при создании;
- из watch‑наблюдателей при изменении входных параметров.
Пример:
export default {
props: {
userId: {
type: Number,
required: true,
}
},
data() {
return {
user: null,
loading: false,
error: null,
}
},
created() {
// Первая инициализация
this.initUser()
},
watch: {
// При изменении userId мы повторяем инициализацию
userId: {
immediate: false,
async handler(newId) {
await this.initUser(newId)
}
}
},
methods: {
async initUser(id = this.userId) {
this.loading = true
this.error = null
try {
this.user = await api.getUser(id)
} catch (err) {
this.error = 'Не удалось загрузить пользователя'
} finally {
this.loading = false
}
}
}
}
Здесь created выполняет начальную инициализацию, а watcher — повторную, при изменении props.
Типичные ошибки при инициализации в created
Смешивание логики DOM и данных
Одна из распространенных ошибок — попытка получить доступ к DOM в created:
created() {
// Так делать нельзя - DOM еще не существует
const el = document.getElementById('some-id') // el будет null
}
Важно помнить:
- в created нужно работать только с данными и логикой;
- все, что связано с разметкой, переносите в mounted или хуки обновления.
Дублирование логики между created и mounted
Бывает, что часть инициализации реализована в created, а часть — в mounted, без реальной необходимости. Это приводит к:
- усложнению понимания, «что где происходит»;
- возможным расхождениям логики.
Решение:
- держать инициализацию данных в одном месте (created или отдельном методе);
- использовать mounted только для DOM‑зависимых действий.
Избыточная инициализация
Иногда в created загружают гораздо больше данных, чем нужно для первого экрана:
- десятки запросов;
- редко используемые справочники;
- второстепенные сущности.
Это увеличивает время первого отображения и ухудшает ощущение скорости.
Рекомендация:
- вынесите второстепенные данные в ленивую загрузку;
- оставьте в created только то, без чего экран не имеет смысла.
Рекомендации по проектированию инициализации
Разделяйте «обязательные» и «дополнительные» данные
Полезно разделить:
- данные, без которых экран бесполезен (например, сам объект редактирования);
- данные, которые улучшают опыт, но не критичны (например, статистика, рекомендации).
В created загружайте:
- обязательный минимум;
- плюс то, что действительно нужно сразу.
Остальное — подгружайте позже, по событию или в фоновом режиме.
Нормализуйте данные на входе
Как только данные попали в компонент (в created):
- приведите ключи к единому стилю;
- распарсите числа и даты;
- создайте вспомогательные флаги.
Так:
- шаблоны становятся проще;
- вычисляемые значения работают с чистой моделью;
- уменьшается количество разрозненных преобразований по коду.
Оставляйте следы в логах
При сложной инициализации полезно:
- логировать ключевые шаги;
- при ошибках указывать контекст (какой id, какой этап не прошел).
Это удобно делать в методах, вызываемых из created, чтобы не перегружать сам хук.
Итог
Инициализация данных в состоянии created — это точка, в которой вы:
- подготавливаете внутреннее состояние компонента до отображения;
- используете входные параметры (props, маршрут, локальное хранилище);
- запускаете асинхронные запросы и нормализуете их результат;
- отделяете «логику данных» от «логики DOM».
Грамотно организованный код инициализации:
- уменьшает количество мигающих и «прыгающих» интерфейсов;
- делает поведение компонента предсказуемым;
- упрощает тестирование и сопровождение.
Если вы держите всю логику подготовки данных в одном хорошо спроектированном месте (часто — в created плюс отдельные методы), вам проще понимать, откуда берется состояние и почему экран выглядит именно так, как выглядит.
Частозадаваемые технические вопросы по теме
Как отменять асинхронные запросы, запущенные в created, если компонент быстро уничтожается
Используйте механизмы отмены запросов (AbortController, токены отмены в клиенте) и сохраняйте контроллер в состоянии компонента. В хуке уничтожения (например, beforeUnmount или beforeDestroy) вызывайте abort, чтобы:
- прервать сетевой запрос;
- избежать попытки записать данные в уже уничтоженный компонент.
Мини‑инструкция:
- В created создайте AbortController.
- Передайте его сигнал в ваш HTTP‑клиент.
- В хуке уничтожения вызовите controller.abort().
- В catch‑блоке проверяйте, не была ли это отмена, и не показывайте пользователю ошибку в этом случае.
Как протестировать логику инициализации в created без реального фреймворка
Вынесите всю логику в отдельную функцию/метод, не зависящий от контекста компонента. В тестах:
- Передавайте в функцию «фейковые» входные данные (props, параметры маршрута).
- Используйте мок‑объекты для API‑вызовов.
- Проверяйте, что функция возвращает ожидаемое состояние или вызывает нужные методы.
Сам created в этом случае просто вызывает эту функцию, а основное тестирование сосредоточено на чистой логике.
Как обработать ситуацию, когда несколько created‑запросов меняют одно и то же состояние
Если есть риск гонок (race conditions), вводите версии или метки времени запросов:
- Перед запуском каждого запроса увеличивайте счетчик версии.
- Сохраняйте локальную копию версии при старте запроса.
- Перед записью результата проверяйте, совпадает ли версия с текущей.
- Если нет — пропускайте запись (данные устарели).
Так более поздние запросы не будут перетирать результат более свежих.
Что делать, если инициализация слишком тяжелая и тормозит интерфейс
Разбейте инициализацию:
- Определите минимальный набор данных, без которого экран не имеет смысла, и грузите его в created.
- Второстепенные данные переносите:
- в отдельные ленивые запросы;
- в хуки после первого рендера;
- в обработчики событий (например, при открытии вкладки).
- При необходимости показывайте skeleton или индикаторы загрузки отдельных блоков, а не всей страницы.
Как переиспользовать одну и ту же логику created в нескольких компонентах
Вынесите инициализацию в:
- отдельный модуль/сервис, который:
- принимает параметры (id, фильтры);
- возвращает подготовленные данные;
- или в mixin/composable/хук (в зависимости от стека), где:
- объявляется общая функция init;
- внутри вызываются необходимые API‑запросы и нормализация.
Далее в каждом компоненте в created вы просто вызываете общий init и подставляете результат в свое локальное состояние.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

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