数据获取选项
详解 useFetch 和 useAsyncData 的所有配置选项。这些选项控制着数据获取的行为——从请求时机、缓存策略到数据转换。
选项总览
| 选项 | 类型 | 默认值 | 一句话说明 |
|---|---|---|---|
key | string | 自动生成 | 缓存键,用于去重和 SSR 传递 |
server | boolean | true | 是否在服务端执行 |
lazy | boolean | false | 是否阻塞导航 |
immediate | boolean | true | 是否立即执行 |
default | () => T | - | 数据加载前的默认值 |
transform | (data) => T | - | 转换响应数据 |
pick | string[] | - | 只提取指定字段 |
watch | WatchSource[] | false | - | 监听源,变化时重新获取 |
deep | boolean | false | 是否使用深度 ref |
dedupe | 'cancel' | 'defer' | 'cancel' | 去重策略 |
timeout | number | - | 请求超时时间(ms) |
getCachedData | function | - | 自定义缓存读取策略 |
method | string | 'GET' | HTTP 方法(仅 useFetch) |
query | object | - | 查询参数(仅 useFetch) |
body | object | - | 请求体(仅 useFetch) |
headers | object | - | 请求头(仅 useFetch) |
baseURL | string | - | 基础 URL(仅 useFetch) |
cache | string | - | 缓存控制(仅 useAsyncData) |
选项分类
- 数据控制:
server、lazy、immediate→ 控制请求时机 - 数据处理:
transform、pick、default→ 控制数据形态 - 缓存策略:
key、dedupe、getCachedData→ 控制缓存行为 - 响应式:
watch、deep→ 控制响应式更新 - HTTP 选项:
method、query、body等 → 控制请求参数(仅 useFetch)
server —— 是否在服务端执行
控制是否在 SSR 时获取数据:
// 仅客户端获取(SSR 时不请求)
const { data } = await useFetch('/api/client-data', {
server: false,
})
// SSR 时也获取(默认)
const { data } = await useFetch('/api/seo-data', {
server: true, // 默认值,可省略
})server: false 的完整行为
- SSR 时:不执行请求,
data为undefined(或default值) - 客户端 Hydration 后:在客户端发起请求
- 客户端导航时:在客户端发起请求
适用场景
server | 场景 | 原因 |
|---|---|---|
true(默认) | SEO 重要的数据、公共数据 | 需要搜索引擎抓取 |
true(默认) | 首屏需要显示的数据 | 用户打开页面就能看到 |
false | 用户个人信息 | SSR 时不知道是哪个用户 |
false | 依赖浏览器 API 的数据 | 服务端没有 window、localStorage |
false | 后台管理数据 | 不需要 SEO |
INFO
️ server: false 时 data 在 SSR 时为 undefined 需要处理这种情况
// ✅ 用 default 设置默认值
const { data } = await useFetch('/api/me', {
server: false,
default: () => ({ name: '', email: '' }),
})
// ✅ 或用 pending 显示加载状态
const { data, pending } = await useFetch('/api/me', { server: false })配合 lazy 使用
// server: false + lazy → SSR 不请求 + 不阻塞页面
const { data, pending } = useLazyFetch('/api/me', {
server: false,
default: () => ({}),
})lazy —— 是否阻塞导航
控制数据加载是否阻塞页面切换:
// 阻塞模式(默认):等待数据加载完成再显示页面
const { data } = await useFetch('/api/data', { lazy: false })
// 非阻塞模式:页面先显示,数据后加载
const { data, pending } = useFetch('/api/data', { lazy: true })
// 等价于 useLazyFetchlazy 对 SSR 的影响
lazy: false:SSR 时等待数据 → 完整 HTML → SEO 友好lazy: true:SSR 时不等待 → HTML 无数据 → SEO 不友好
TIP
详见 懒加载获取 章节
immediate —— 是否立即执行
控制是否在组件 setup 时立即发起请求:
// 不自动执行——需要手动触发
const { data, execute, status } = await useFetch('/api/search', {
immediate: false, // status 初始为 'idle'
})
// 手动触发
async function handleSearch() {
await execute()
// status 变为 'success' 或 'error'
}immediate: false 时 status 为 'idle'
idle→ 还未开始(仅immediate: false时出现)pending→ 正在加载success→ 加载成功error→ 加载失败
典型场景——搜索功能
const keyword = ref('')
const { data, execute, status } = await useFetch('/api/search', {
query: { q: keyword },
immediate: false, // 页面加载时不搜索
})
// 用户点击搜索按钮才执行
async function search() {
if (!keyword.value.trim()) return
await execute()
}典型场景——分页 + 手动加载
const page = ref(1)
const { data, execute } = await useFetch('/api/users', {
query: { page },
immediate: false,
})
// 手动触发首次加载
onMounted(() => execute())
// 翻页时重新执行
function nextPage() {
page.value++
execute()
}INFO
️ immediate: false + watch 的交互
immediate: false:不立即执行watch监听的值变化时:会自动执行- 如果想要"既不立即执行,也不自动监听":设置
watch: false
watch —— 监听变化自动刷新
监听响应式数据的变化,变化时自动重新获取数据:
const page = ref(1)
const searchQuery = ref('')
const { data } = await useFetch('/api/users', {
query: { page, q: searchQuery },
watch: [page, searchQuery], // 任一变化都重新获取
})watch 的注意事项
- 只有
watch数组中的 ref 变化才会触发重新请求——query中的 ref 不会自动触发 - 可以传
false完全禁用自动监听 watch触发时不会重置data——旧数据保留直到新数据到达
INFO
️ 常见错误:在 query 中用了 ref 但忘记加到 watch
const page = ref(1)
// ❌ page 变化时不会重新请求
const { data } = await useFetch('/api/users', {
query: { page },
})
// ✅ 显式加入 watch
const { data } = await useFetch('/api/users', {
query: { page },
watch: [page],
})高级——用计算属性监听
const route = useRoute()
// 监听路由参数变化
const { data } = await useFetch('/api/posts', {
query: { category: route.query.category },
watch: [() => route.query.category], // 监听特定查询参数
})transform —— 转换响应数据
在数据传递给响应式系统和 Payload 之前,对数据进行转换:
const { data } = await useFetch('/api/users', {
transform: (response) => {
// 转换数据结构
return response.data.map(user => ({
...user,
fullName: `${user.firstName} ${user.lastName}`,
}))
},
})transform vs 在模板中处理
| 方式 | 执行时机 | Payload 大小 | 适用场景 |
|---|---|---|---|
transform | 服务端,数据传递到 Payload 前 | 更小(只传递转换后的数据) | 减少数据量、过滤敏感字段 |
| 模板中处理 | 客户端,渲染时 | 更大(全量数据) | 简单格式化 |
如果 API 返回的数据量很大
推荐用 transform 或 pick 减少 Payload 大小 。
transform 的进阶用法
// 1. 数据扁平化
const { data } = await useFetch('/api/users', {
transform: (response) => response.data.items, // 只取 items 数组
})
// 2. 添加计算字段
const { data } = await useFetch('/api/orders', {
transform: (orders) => orders.map(o => ({
...o,
total: o.price * o.quantity, // 计算总价
})),
})
// 3. 数据分组
const { data } = await useFetch('/api/posts', {
transform: (posts) => {
return posts.reduce((acc, post) => {
const year = new Date(post.date).getFullYear()
if (!acc[year]) acc[year] = []
acc[year].push(post)
return acc
}, {})
},
})pick —— 只提取指定字段
从响应中只提取指定的字段,减少 Payload 大小:
// API 返回:{ id: 1, name: 'Alice', email: 'a@b.com', password: '...', createdAt: '...' }
const { data } = await useFetch('/api/user/1', {
pick: ['id', 'name', 'email'],
})
// data: { id: 1, name: 'Alice', email: 'a@b.com' }pick 的好处
- 减小 Payload:不需要的字段不会传递给客户端
- 安全性:过滤掉
password、token等敏感字段 - 性能:客户端需要处理的数据更少
INFO
️ pick 只能提取顶层字段 嵌套字段需要用 transform
pick vs transform
// pick:简单提取顶层字段
const { data } = await useFetch('/api/user/1', {
pick: ['id', 'name', 'email'],
})
// transform:更灵活,可以提取嵌套字段、做计算
const { data } = await useFetch('/api/user/1', {
transform: (user) => ({
id: user.id,
name: user.profile.name, // 嵌套字段
initials: user.name[0], // 计算字段
}),
})default —— 设置默认值
数据加载完成前的默认值,避免 data 为 undefined 导致模板报错:
// ❌ 不设 default
const { data } = await useFetch('/api/users')
// data 初始为 undefined,v-for="user in data" 会报错
// ✅ 设置 default
const { data } = await useFetch('/api/users', {
default: () => [], // data 初始为 [],v-for 安全
})什么时候需要 default?
lazy: true或useLazyFetch:数据初始为undefinedserver: false:SSR 时data为undefined- API 可能返回空值时
default 必须是函数
default: () => [] ✅,default: [] ❌。
常见 default 值
// 列表
default: () => []
// 单个对象
default: () => ({})
// 带默认字段的对象
default: () => ({ name: '', email: '', avatar: '/default.png' })
// 数字
default: () => 0
// null(允许为空但避免 undefined)
default: () => nulldedupe —— 去重策略
处理多个组件同时请求相同 key 的数据时,如何处理重复请求:
// cancel:取消之前的请求(默认)
const { data } = await useFetch('/api/users', {
dedupe: 'cancel',
})
// defer:等待之前的请求完成,共享结果
const { data } = await useFetch('/api/users', {
dedupe: 'defer',
})cancel vs defer
| 策略 | 行为 | 适用场景 |
|---|---|---|
cancel | 新请求到来时,取消上一个未完成的请求 | 数据可能频繁变化的场景(如搜索) |
defer | 如果已有相同 key 的请求在进行中,等待它完成并共享结果 | 数据不常变化的场景(如配置、字典) |
典型场景
// 搜索场景:用 cancel(用户快速输入时,只保留最后一次搜索)
const { data } = await useFetch('/api/search', {
query: { q: keyword },
watch: [keyword],
dedupe: 'cancel',
})
// 配置场景:用 defer(多个组件需要同一份配置,共享请求)
const { data } = await useFetch('/api/config', {
dedupe: 'defer',
})getCachedData —— 自定义缓存策略
完全控制何时使用缓存、何时重新获取:
const { data } = await useFetch('/api/data', {
getCachedData(key, nuxtApp, ctx) {
// ctx.cause 告诉你为什么触发了这次获取
// 'initial':首次加载
// 'refresh:manual':手动 refresh()
// 'refresh:hook':由 Nuxt 钩子触发
// 'watch':watch 监听的值变化
if (ctx.cause === 'initial') {
// 首次加载:使用 Payload 数据
return nuxtApp.payload.data[key]
}
// 其他情况:返回 undefined,重新获取
},
})实战——基于时间的缓存
const FIVE_MINUTES = 5 * 60 * 1000
const { data } = await useFetch('/api/articles', {
getCachedData(key, nuxtApp) {
const cached = nuxtApp.static.data[key]
if (!cached) return undefined // 无缓存
// 检查缓存是否过期
if (Date.now() - cached.timestamp > FIVE_MINUTES) {
return undefined // 过期,重新获取
}
return cached.data // 使用缓存
},
})getCachedData 返回值的含义
- 返回非
undefined→ 使用缓存数据,不再请求 - 返回
undefined→ 重新获取数据
nuxtApp.payload.data vs nuxtApp.static.data
payload.data:SSR 时从服务端传递过来的数据(只在 Hydration 时有)static.data:客户端的静态缓存(页面导航后仍然存在)
deep —— 是否使用深度 ref
控制 data ref 是否使用深度响应式:
// 浅 ref(默认,性能更好)
const { data } = await useFetch('/api/data', {
deep: false,
})
// data.value 的嵌套属性变化不会触发更新
// 深 ref(嵌套对象变化也能触发更新)
const { data } = await useFetch('/api/data', {
deep: true,
})
// data.value 的嵌套属性变化也会触发更新何时用 deep: true?
- 你需要直接修改
data.value中的嵌套属性并希望触发 UI 更新 - 大多数情况下用
deep: false(默认)就够了,因为数据变化通常是重新获取
INFO
️ deep: true 的性能影响:深度 ref 需要递归遍历整个对象 数据量大时会影响性能
timeout —— 请求超时
const { data } = await useFetch('/api/slow', {
timeout: 5000, // 5 秒超时
})INFO
️ 超时行为:超时后 error ref 会被设置为超时错误 status 变为 'error'
响应式选项
所有 fetch 选项(query、params、headers 等)都支持响应式值:
const page = ref(1)
const sort = ref('name')
const limit = ref(20)
const { data } = await useFetch('/api/users', {
query: computed(() => ({
page: page.value,
sort: sort.value,
limit: limit.value,
})),
})注意
响应式选项变化不会自动触发重新请求。如果你希望选项变化时自动重新获取,需要配合 watch:
const page = ref(1)
const { data } = await useFetch('/api/users', {
query: { page },
watch: [page], // page 变化时重新请求
})选项组合实战
搜索 + 分页
const keyword = ref('')
const page = ref(1)
const { data, status } = await useFetch('/api/search', {
query: computed(() => ({
q: keyword.value,
page: page.value,
})),
watch: [keyword, page],
dedupe: 'cancel', // 快速输入时只保留最后一次
default: () => ({ items: [], total: 0 }),
})仅客户端 + 懒加载 + 默认值
const { data, pending } = useLazyFetch('/api/me', {
server: false, // SSR 不请求
default: () => ({ name: '' }), // 避免未定义报错
})手动触发 + 条件请求
const { data, execute, status } = await useFetch('/api/export', {
method: 'POST',
body: { format: 'csv', dateRange: dateRange.value },
immediate: false, // 不自动执行
timeout: 30000, // 导出可能较慢
})
// 用户点击"导出"按钮才执行
async function handleExport() {
await execute()
if (data.value?.url) {
window.open(data.value.url)
}
}知识脉络
useFetch → useAsyncData → $fetch → 懒加载获取
│
└─→ 你在这里:数据获取选项
│
├─→ 相关:缓存与刷新(getCachedData / dedupe 详解)
│
└─→ 相关:SSR 数据传递(server / Payload 详解)