Nuxt4 数据获取进阶
掌握了 useFetch 的基本用法后,这篇学习控制请求时机、延迟加载等进阶选项,以及 Nuxt 4.4 新增的工厂模式,把数据获取做得规范可维护。
常用选项
server(是否在服务端执行)
默认 true,数据在服务端请求。设为 false 则只在客户端请求,适合纯客户端功能:
<script setup>
const { data } = await useFetch('/api/private-data', {
server: false
})
</script>
设为 false 的请求不会参与 SSR,首屏没有该数据,客户端 hydration 后才请求。
lazy(延迟加载)
默认 false,页面会等待数据准备好才渲染(配合 await)。设为 true 则页面先渲染,数据到了再更新:
<script setup>
const { data, pending } = useFetch('/api/slow-data', {
lazy: true
})
</script>
<template>
<!-- pending 期间页面已经渲染,这里显示加载状态 -->
<div v-if="pending">加载中...</div>
<div v-else>{{ data }}</div>
</template>
适合慢接口,让页面骨架先出来。useLazyFetch 和 useLazyAsyncData 是 lazy 的快捷写法,等同于默认 lazy: true。
default(默认值)
请求完成前 data 是 null,用 default 给一个初始值:
<script setup>
const { data } = await useFetch('/api/user', {
default: () => ({ name: '匿名用户', age: 0 })
})
</script>
<template>
<p>{{ data.name }}</p>
</template>
配合 lazy 或 immediate 场景很实用,模板里不用到处判断 null。
transform(数据转换)
对请求结果做处理,转换逻辑会应用到双端:
<script setup>
const { data: posts } = await useFetch('/api/posts', {
// 只保留需要的字段
transform: (res) => res.map(p => ({ id: p.id, title: p.title }))
})
</script>
immediate(是否立即请求)
设为 false 时初始化不请求,配合 execute 手动触发:
<script setup>
const { data, execute } = useFetch('/api/search', {
immediate: false,
query: { keyword: '' }
})
function search() {
execute() // 手动发起请求
}
</script>
watch(监听触发)
watch 选项监听指定的响应式值,变化时自动重新请求:
<script setup>
const page = ref(1)
const { data } = await useFetch('/api/posts', {
query: { page },
watch: [page] // page 变化时自动重新请求
})
</script>
提示:useFetch 的 options 中传入响应式值(如 query 传 ref)时,默认就会自动监听变化并重新请求,不需要额外写 watch。想要关闭自动监听时显式传 watch: false。
刷新数据
refresh 重新执行请求:
<script setup>
const { data, refresh } = await useFetch('/api/users')
function reload() {
refresh() // 重新请求
}
</script>
工厂模式 createUseFetch
Nuxt 4.4 引入了 createUseFetch 和 createUseAsyncData,可以创建带有自定义默认选项的请求实例。这样项目中所有请求的 baseURL、请求头、错误处理都可以统一管理。
在 app/composables/ 下创建 useApiFetch:
// app/composables/useApiFetch.ts
export const useApiFetch = createUseFetch((options) => {
const runtimeConfig = useRuntimeConfig()
return {
...options,
// 统一 baseURL,从运行时配置读取
baseURL: options.baseURL ?? runtimeConfig.public.apiBase,
// 统一请求头
headers: {
...options.headers,
Authorization: 'Bearer token'
}
}
})
使用起来和 useFetch 完全一样:
<script setup>
// 自动带上 baseURL 和请求头
const { data } = await useApiFetch('/users')
</script>
也可以创建简单配置的实例:
// app/composables/useClientFetch.ts
// 统一只在客户端请求
export const useClientFetch = createUseFetch({
server: false
})
工厂模式的好处:
| 场景 | 传统写法 | 工厂模式 |
|---|---|---|
| 统一 baseURL | 每个页面写一遍 | 实例里写一次 |
| 统一请求头 | 每个页面传一遍 headers | 实例里写一次 |
| 统一关闭 SSR | 每个调用传 server: false |
实例里写一次 |
| 接口路径变化 | 到处改 URL | 只改实例 |
提示:createUseFetch 需要在 composables/ 目录中导出才能生效。函数式写法(传回调)时,回调里可以拿到调用者传入的 options,适合动态计算默认值。
常见坑
| 坑 | 解决办法 |
|---|---|
| URL 用模板字符串拼响应式值 | 改用函数形式 useFetch(() => url) |
| 在条件语句里调用 useFetch | 不要条件调用,用 immediate: false 加 execute |
| 在事件回调里调用 useFetch | 事件里用 $fetch,数据层逻辑留在 setup |
| 忘记 key 导致数据不共享 | 跨组件共享用 useAsyncData 显式传相同 key |
一句话总结:用 server、lazy、immediate 控制请求时机,用 transform 处理数据,用 createUseFetch 统一项目里的请求规范。