现在位置: 首页 > Vue3 教程 > 正文

生命周期与数据请求

本章你将学会 Vue3 的生命周期钩子,并在合适的时机用 fetch 从 JSON 文件加载博客数据。


什么是生命周期?

每个 Vue 组件从创建到销毁,会经历一系列阶段,这些阶段称为「生命周期」。

Vue 在每个阶段提供对应的 钩子函数,让你在特定的时间点执行代码。

比如:你想在组件加载完成后去请求数据,就需要用到 onMounted 这个钩子。


Vue3 生命周期钩子总览

钩子函数执行时机常用场景
setup()组件初始化,最早执行定义响应式数据、计算属性
onBeforeMount组件挂载到 DOM 之前较少使用
onMounted组件挂载到 DOM 之后发起网络请求、初始化第三方库、获取 DOM 引用
onBeforeUpdate数据变化、DOM 更新之前较少使用
onUpdated数据变化、DOM 更新之后DOM 更新后的操作
onBeforeUnmount组件销毁之前清除定时器、取消订阅、移除事件监听
onUnmounted组件销毁之后清理工作确认

对于博客项目,我们最常用的是 onMounted——在页面加载后去获取文章数据。

生命周期钩子必须写在 <script setup> 中,且不能放在异步函数或条件语句内部。Vue 通过调用顺序来判断它们属于哪个组件。


onMounted 的基本使用

实例

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

const message = ref('加载中...')

// onMounted 在组件挂载到页面后执行
onMounted(() => {
  // 此时 DOM 已经渲染完成,可以安全地操作 DOM 或发请求
  console.log('组件已挂载,可以开始加载数据了')
  message.value = '数据加载完成!'
})
</script>

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

一个组件可以调用多次 onMounted,它们会按注册顺序依次执行。


用 fetch 加载 JSON 数据

我们准备把文章数据放到一个 JSON 文件中,然后在 onMounted 中用 fetch 加载它。

第一步:创建数据文件

实例

// 文件路径:public/posts.json
[
  {
    "id": 1,
    "title": "Vue3 入门完全指南",
    "summary": "从零开始学习 Vue3 组合式 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 中的文件不会被编译,原样复制到构建产物中。

第二步:在组件中 fetch 数据

实例

<!-- 文件路径: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>

处理异步请求的三种状态

任何数据请求都应覆盖以下三种 UI 状态,否则用户体验会很差:

状态何时出现UI 表现
加载中请求发出后、响应返回前loading 动画、骨架屏
成功数据正确返回正常渲染内容
失败网络错误、服务器异常错误提示 + 重试按钮

你还可以额外处理第四种状态——空数据(请求成功但返回空数组),给用户明确提示而非空白页面。


动手:详情页也改用 fetch 加载

详情页需要根据 URL 中的 ID,从 JSON 中找到对应的文章。

可以把数据加载逻辑复用——在详情页也调用 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('加载失败')
    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 从 public 目录加载 JSON 数据、以及如何处理请求的四种 UI 状态(加载/成功/失败/空)。

现在博客数据从外部 JSON 文件异步加载,更接近真实项目的工作方式。