六维教程

Vue3 数据请求

组件布局、交互、样式都学过了,但页面真正的”内容”通常来自后端接口,例如:文章列表、商品详情、用户信息。

数据请求就是把接口拿到的数据变成页面元素,这是 SPA 开发和”数据驱动视图”的临门一脚。

在哪个时机请求 onMounted

组件挂载后页面就绪,此时发请求最自然(生命周期篇的伏笔在这兑现),基本流程是”三个状态”:

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

const list = ref([])       // 数据
const loading = ref(true)  // 加载中
const error = ref('')      // 错误信息

onMounted(async () => {
  try {
    const res = await fetch('/api/articles')
    list.value = await res.json()
  } catch (e) {
    error.value = '加载失败,请稍后重试'
  } finally {
    loading.value = false
  }
})
</script>

<template>
  <p v-if="loading">加载中…</p>
  <p v-else-if="error">{{ error }}</p>
  <ul v-else>
    <li v-for="item in list" :key="item.id">{{ item.title }}</li>
  </ul>
</template>

模板里的三段式(加载中 / 出错 / 成功)几乎是数据页面的标配骨架。

请求回来的数据没触发页面更新

多半是忘了把结果赋给响应式变量list.value = data(ref),而不是把 data 随便扔哪儿。

另一种是赋给了普通变量,页面自然不动。

为什么要在 onMounted 里请求

写在顶层也能执行,但顶层代码在”组件创建阶段”就运行,组件还没挂载、环境未就绪,拿 DOM、做初始化都不合适。

请求数据这种”页面就绪后再做”的事,放 onMounted

带参数的请求

把数据变化折叠成 URL 参数,用模板字符串或 URLSearchParams 都可以:

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

const keyword = ref('')
const result = ref([])

// 关键词一变化就重新请求
watch(keyword, (val) => {
  const url = `/api/search?q=${encodeURIComponent(val)}`
  fetch(url).then(r => r.json()).then(data => result.value = data)
})
</script>

<template>
  <input v-model="keyword" placeholder="搜索" />
</template>

上面的例子参数只有一个,直接拼模板字符串最省事。

可一旦参数变多、还带中文,手拼就容易乱,还容易漏掉编码。

这种场景推荐 URLSearchParams,把参数丢给它,它会负责拼接和自动编码:

const params = new URLSearchParams({
  q: keyword.value,
  page: 1,
  size: 20
})

const url = `/api/search?${params.toString()}`
// url 结果为 /api/search?q=%E5%85%B3...&page=1&size=20

params.toString() 会把中文、空格这些特殊字符自动转成 %xx 形式,所以不用再手动 encodeURIComponent,参数再多也不会拼错。

POST 与其他方法

fetch 默认是 GET,要 POST、带请求头、带 body 时,传入第二个参数:

const res = await fetch('/api/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ username: 'tom', password: '123456' })
})
const user = await res.json()

用 Axios 吗

fetch 是浏览器内置,够用且不用装依赖,本系列示例都用它。

但项目里更常见的是 Axiosnpm install axios),它把常用的处理收得更顺手:

import axios from 'axios'

// 全局配置一次(见 main.js)
axios.defaults.baseURL = '/api'
axios.defaults.withCredentials = true

// 发起请求:直接用 .data 拿到响应体
const { data } = await axios.get('/articles', { params: { page: 1 } })

相对 fetch,Axios 的优势是:自动转 JSON、错误统一走 catch、拦截器、取消请求等。

新手先用 fetch 把概念学透,接入 Axios 只是换一行 API

中止与防抖

请求是异步的,从发出到拿到结果要等一段时间,这段空档里页面可能已经变了,有两个典型的坑。

  • 切走页面请求还没回来:组件已经卸载,请求才返回,这时再给组件里的状态赋值,控制台会告警。

  • 搜索框打字太快:每敲一个字就发一次请求,前面的还没回来又发了一堆,接口扛不住也浪费。

对应两个手段,中止和防抖。

中止请求 AbortController

问题一用中止解决,组件卸载时把还没回来的请求取消掉,响应自然也不会去碰已卸载的组件。

AbortController 是浏览器内置的”请求开关”,用法分三步:

  1. 第一步 new AbortController() 创建开关
  2. 第二步把它的 controller.signal 传给 fetch 的配置项,表示”这个请求听这个开关的”
  3. 第三步想取消时调用 controller.abort(),请求就会被掐断,正在 await fetch(...) 的代码抛出 AbortError

开关一创一掐,请求的生命周期就握在手里了:

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

const list = ref([])
const controller = new AbortController()   // ① 创建开关

onMounted(async () => {
  try {
    const res = await fetch('/api/articles', { signal: controller.signal })  // ② 请求听开关的
    list.value = await res.json()
  } catch (e) {
    // 手动中止会抛 AbortError,这里忽略即可
  }
})

onBeforeUnmount(() => controller.abort())   // ③ 组件卸载时掐断请求
</script>

防抖

问题二用防抖解决,只在用户”停手”之后才发请求,打字期间的请求都作废:

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

const keyword = ref('')
let timer = null

watch(keyword, (val) => {
  clearTimeout(timer)          // 清了上一次,只认最后一次
  timer = setTimeout(() => {
    fetch(`/api/search?q=${encodeURIComponent(val)}`)
      .then(r => r.json())
      .then(data => result.value = data)
  }, 300)                      // 停手 300ms 后才真正发请求
})
</script>

高频场景可以再叠加中止,防抖负责”少发”,中止负责”切走就停”,两个配合基本就稳了。

速查卡片

场景 写法
挂载后请求 onMounted(async () => { ... })
三段式 UI v-if="loading" / v-else-if="error" / v-else
GET 带参 拼 URL 或 URLSearchParams
POST / 头部 fetch(url, { method, headers, body })
Axios 推荐 .get(url, { params }),用 data 取值
卸载中止 AbortController + onBeforeUnmount
高频搜索 防抖 + 中止
上一篇
Vue3 动态组件与缓存
下一篇
Vue3 组合式函数