Жизненный цикл и запросы данных

В этой главе вы изучите хуки жизненного цикла Vue3 и научитесь использовать fetch для загрузки данных блога из JSON-файла в нужный момент.


Что такое жизненный цикл?

Каждый компонент проходит через ряд этапов от создания до уничтожения; эти этапы называются "жизненным циклом".

Vue предоставляет соответствующиефункции-хукина каждом этапе, позволяя вам выполнять код в определённые моменты времени.

Например, если вы хотите запросить данные после завершения загрузки компонента, вам нужно использоватьonMountedэтот хук.


Обзор хуков жизненного цикла Vue3

Хук-функцияВремя выполненияТипичные случаи использования
setup()Инициализация компонента, выполняется самым первымОпределение реактивных данных и вычисляемых свойств
onBeforeMountПеред монтированием компонента в DOMИспользуется реже
onMountedПосле монтирования компонента в DOMВыполнение сетевых запросов, инициализация сторонних библиотек, получение ссылок на DOM
onBeforeUpdateПеред изменением данных и обновлением DOMИспользуется реже
onUpdatedПосле изменения данных и обновления DOMОперации после обновления DOM
onBeforeUnmountПеред уничтожением компонентаОчистить таймеры, отменить подписки, удалить обработчики событий
onUnmountedПосле уничтожения компонентаПодтвердить выполнение очистки

В проекте блога чаще всего мы используемonMounted—получение данных статьи после загрузки страницы.

Хуки жизненного цикла должны быть написаны в<script setup>setup, и не могут быть помещены внутрь асинхронных функций или условных операторов. Vue определяет, какому компоненту они принадлежат, по порядку вызовов.


Базовое использование onMounted

Пример

<script setup>
import { ref, onMounted } from 'vue'

const message = ref('Loading...')

// onMounted выполняется после монтирования компонента на страницу
onMounted(() => {
// На этом этапе DOM завершил рендеринг, поэтому можно безопасно манипулировать DOM или отправлять запросы.
console.log('Component has mounted, can start loading data now')
message.value = 'Data loading complete!'
})
</script>

<template>
  <p>{{ message }}</p>
</template>

Компонент может вызывать onMounted несколько раз, и они будут выполняться в порядке регистрации.


Загрузка данных JSON с помощью fetch

Мы поместим данные статьи в файл JSON, затем используем fetch в onMounted для их загрузки.

Шаг 1: Создайте файл данных

Пример

// Путь к файлу: public/posts.json
[
  {
    "id": 1,
"title": "Vue3: полное руководство для начинающих",
"summary": "Изучение Vue3 с нуля: Composition API, включая ref, reactive, computed и основные концепции.",
"content": "<h2>Почему стоит изучать Vue3?</h2><p>Vue3 — один из самых популярных современных фронтенд-фреймворков...</p>",
    "category": "Vue",
    "date": "2024-05-10"
  },
  {
    "id": 2,
"title": "Асинхронное программирование в JavaScript: подробный разбор"
"summary": "Одна статья, чтобы разобраться в Promise, async/await, событийном цикле и очереди микрозадач.",
"content": "<h2>Что такое асинхронность?</h2><p>JS однопоточный...</p>",
    "category": "JavaScript",
    "date": "2024-05-08"
  },
  {
    "id": 3,
"title": "CSS Grid на практике"
"summary": "Легко реализуйте сложные адаптивные макеты с помощью CSS Grid.",
"content": "<h2>Начало работы с Grid</h2><p>Grid — это двумерная система компоновки...</p>",
    "category": "CSS",
    "date": "2024-05-05"
  }
]

JSON-файл размещается вpublic/каталоге. Vite будет отдавать его напрямую как статический файл. В браузере вы можете обратиться к нему по/posts.jsonнапрямую. Файлы, помещённые в public, не компилируются; они копируются как есть в результат сборки.

Шаг 2: Получение данных в компоненте

Пример

<!-- Путь к файлу: src/views/HomeView.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import BlogCard from '../components/BlogCard.vue'
import CategoryFilter from '../components/CategoryFilter.vue'

const articles = ref([]) // изначально пустой массив
const isLoading = ref(true) // состояние загрузки
const error = ref(null) // сообщение об ошибке

// Асинхронно загрузить данные статей
async function fetchPosts() {
  isLoading.value = true
  error.value = null
  try {
    const response = await fetch('/posts.json')
// Проверить, успешен ли ответ (выбрасывает ошибку при 404 или 500)
    if (!response.ok) {
throw new Error(`Ошибка загрузки: HTTP ${response.status}`)
    }
    const data = await response.json()
    articles.value = data
  } catch (err) {
    error.value = err.message
console.error('Не удалось загрузить данные статьи:', err)
  } finally {
    isLoading.value = false
  }
}

// Загрузить данные сразу после монтирования компонента
onMounted(() => {
  fetchPosts()
})
</script>

<template>
  <div class="home">
    <h2>Последние статьи</h2>

    <!-- Состояние загрузки -->
    <p v-if="isLoading" class="status-msg">Загрузка, пожалуйста, подождите...</p>

    <!-- Состояние ошибки -->
    <p v-else-if="error" class="status-msg error">
Не удалось загрузить: {{ error }}
      <button @click="fetchPosts">Повторить</button>
    </p>

    <!-- Состояние пустых данных -->
    <p v-else-if="articles.length === 0" class="status-msg">
Пока нет статей, пожалуйста, следите за обновлениями.
    </p>

    <!-- Обычное отображение данных -->
    <template v-else>
      <div class="article-grid">
        <BlogCard
         v-for="article in articles"
         :key="article.id"
         :id="article.id"
         :title="article.title"
         :summary="article.summary"
         :date="article.date"
         :category="article.category"
       />
      </div>
    </template>
  </div>
</template>

<style scoped>
.status-msg {
  text-align: center;
  padding: 60px 0;
  font-size: 16px;
  color: #999;
}
.error { color: #e74c3c; }
.error button {
  margin-left: 10px;
  padding: 4px 12px;
  cursor: pointer;
}
</style>

Обработка трёх состояний асинхронных запросов

Каждый запрос данных должен охватывать следующие три состояния интерфейса, иначе пользовательский опыт будет плохим:

StateКогда появляетсяПредставление в интерфейсе
ЗагрузкаПосле отправки запроса, до возврата ответаАнимация загрузки, скелетон-экран
УспехДанные возвращаются корректноОбычный вывод содержимого
FailureСетевая ошибка, исключение сервераСообщение об ошибке + кнопка повтора

Вы также можете обработать четвёртое состояние —пустые данные(запрос успешен, но возвращает пустой массив), давая пользователям понятное сообщение вместо пустой страницы.


Практика: также переключите страницу деталей на загрузку с fetch

Страница деталей должна найти соответствующую статью в JSON по ID из URL.

Вы можете переиспользовать логику загрузки данных — также вызвать fetch на странице деталей, а затем отфильтровать.

Пример

<!-- Путь к файлу: src/views/PostView.vue -->
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, RouterLink } from 'vue-router'

const route = useRoute()
const id = Number(route.params.id)

const posts = ref([]) // все посты
const isLoading = ref(true)
const error = ref(null)

// Найти текущую статью по ID
const article = computed(() => {
  return posts.value.find(p => p.id === id)
})

// Загрузить все данные статьи
async function fetchPosts() {
  isLoading.value = true
  try {
    const res = await fetch('/posts.json')
if (!res.ok) throw new Error('Failed to load')
    posts.value = await res.json()
  } catch (err) {
    error.value = err.message
  } finally {
    isLoading.value = false
  }
}

onMounted(() => {
  fetchPosts()
})
</script>

<template>
  <div class="post-view">
    <p v-if="isLoading">Загрузка...</p>
    <p v-else-if="error">Не удалось загрузить: {{ error }}</p>
    <div v-else-if="!article">
      <h2>Статья не существует</h2>
      <RouterLink to="/">Назад на главную</RouterLink>
    </div>
    <article v-else>
      <span class="category-tag">{{ article.category }}</span>
      <h1>{{ article.title }}</h1>
      <time>{{ article.date }}</time>
      <div class="content" v-html="article.content"></div>
      <RouterLink to="/">← Назад на главную</RouterLink>
    </article>
  </div>
</template>

Краткое содержание главы

В этой главе вы освоили самый важный хук жизненного цикла Vue3, onMounted, использовали fetch для загрузки JSON-данных из каталога public и научились обрабатывать четыре состояния UI запроса (загрузка/успех/ошибка/пусто).

Теперь данные блога загружаются асинхронно из внешнего JSON-файла, что ближе к тому, как работают реальные проекты.

Другие расширения