Skip to content

数据获取选项

详解 useFetchuseAsyncData 的所有配置选项。这些选项控制着数据获取的行为——从请求时机、缓存策略到数据转换。

选项总览

选项类型默认值一句话说明
keystring自动生成缓存键,用于去重和 SSR 传递
serverbooleantrue是否在服务端执行
lazybooleanfalse是否阻塞导航
immediatebooleantrue是否立即执行
default() => T-数据加载前的默认值
transform(data) => T-转换响应数据
pickstring[]-只提取指定字段
watchWatchSource[] | false-监听源,变化时重新获取
deepbooleanfalse是否使用深度 ref
dedupe'cancel' | 'defer''cancel'去重策略
timeoutnumber-请求超时时间(ms)
getCachedDatafunction-自定义缓存读取策略
methodstring'GET'HTTP 方法(仅 useFetch)
queryobject-查询参数(仅 useFetch)
bodyobject-请求体(仅 useFetch)
headersobject-请求头(仅 useFetch)
baseURLstring-基础 URL(仅 useFetch)
cachestring-缓存控制(仅 useAsyncData)

选项分类

  • 数据控制serverlazyimmediate → 控制请求时机
  • 数据处理transformpickdefault → 控制数据形态
  • 缓存策略keydedupegetCachedData → 控制缓存行为
  • 响应式watchdeep → 控制响应式更新
  • HTTP 选项methodquerybody 等 → 控制请求参数(仅 useFetch)

server —— 是否在服务端执行

控制是否在 SSR 时获取数据:

ts
// 仅客户端获取(SSR 时不请求)
const { data } = await useFetch('/api/client-data', {
  server: false,
})

// SSR 时也获取(默认)
const { data } = await useFetch('/api/seo-data', {
  server: true,  // 默认值,可省略
})

server: false 的完整行为

  1. SSR 时:不执行请求dataundefined(或 default 值)
  2. 客户端 Hydration 后:在客户端发起请求
  3. 客户端导航时:在客户端发起请求

适用场景

server场景原因
true(默认)SEO 重要的数据、公共数据需要搜索引擎抓取
true(默认)首屏需要显示的数据用户打开页面就能看到
false用户个人信息SSR 时不知道是哪个用户
false依赖浏览器 API 的数据服务端没有 windowlocalStorage
false后台管理数据不需要 SEO

INFO

server: falsedata 在 SSR 时为 undefined 需要处理这种情况

ts
// ✅ 用 default 设置默认值
const { data } = await useFetch('/api/me', {
server: false,
default: () => ({ name: '', email: '' }),
})

// ✅ 或用 pending 显示加载状态
const { data, pending } = await useFetch('/api/me', { server: false })

配合 lazy 使用

ts
// server: false + lazy → SSR 不请求 + 不阻塞页面
const { data, pending } = useLazyFetch('/api/me', {
  server: false,
  default: () => ({}),
})

lazy —— 是否阻塞导航

控制数据加载是否阻塞页面切换:

ts
// 阻塞模式(默认):等待数据加载完成再显示页面
const { data } = await useFetch('/api/data', { lazy: false })

// 非阻塞模式:页面先显示,数据后加载
const { data, pending } = useFetch('/api/data', { lazy: true })
// 等价于 useLazyFetch

lazy 对 SSR 的影响

  • lazy: false:SSR 时等待数据 → 完整 HTML → SEO 友好
  • lazy: true:SSR 时不等待 → HTML 无数据 → SEO 不友好

TIP

详见 懒加载获取 章节

immediate —— 是否立即执行

控制是否在组件 setup 时立即发起请求:

ts
// 不自动执行——需要手动触发
const { data, execute, status } = await useFetch('/api/search', {
  immediate: false,  // status 初始为 'idle'
})

// 手动触发
async function handleSearch() {
  await execute()
  // status 变为 'success' 或 'error'
}

immediate: falsestatus'idle'

  • idle → 还未开始(仅 immediate: false 时出现)
  • pending → 正在加载
  • success → 加载成功
  • error → 加载失败

典型场景——搜索功能

ts
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()
}

典型场景——分页 + 手动加载

ts
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 —— 监听变化自动刷新

监听响应式数据的变化,变化时自动重新获取数据:

ts
const page = ref(1)
const searchQuery = ref('')

const { data } = await useFetch('/api/users', {
  query: { page, q: searchQuery },
  watch: [page, searchQuery],  // 任一变化都重新获取
})

watch 的注意事项

  1. 只有 watch 数组中的 ref 变化才会触发重新请求——query 中的 ref 不会自动触发
  2. 可以传 false 完全禁用自动监听
  3. watch 触发时不会重置 data——旧数据保留直到新数据到达

INFO

常见错误:在 query 中用了 ref 但忘记加到 watch

ts
const page = ref(1)

// ❌ page 变化时不会重新请求
const { data } = await useFetch('/api/users', {
query: { page },
})

// ✅ 显式加入 watch
const { data } = await useFetch('/api/users', {
query: { page },
watch: [page],
})

高级——用计算属性监听

ts
const route = useRoute()

// 监听路由参数变化
const { data } = await useFetch('/api/posts', {
  query: { category: route.query.category },
  watch: [() => route.query.category],  // 监听特定查询参数
})

transform —— 转换响应数据

在数据传递给响应式系统和 Payload 之前,对数据进行转换:

ts
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 返回的数据量很大

推荐用 transformpick 减少 Payload 大小 。

transform 的进阶用法

ts
// 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 大小:

ts
// 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 的好处

  1. 减小 Payload:不需要的字段不会传递给客户端
  2. 安全性:过滤掉 passwordtoken 等敏感字段
  3. 性能:客户端需要处理的数据更少

INFO

pick 只能提取顶层字段 嵌套字段需要用 transform

pick vs transform

ts
// 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 —— 设置默认值

数据加载完成前的默认值,避免 dataundefined 导致模板报错:

ts
// ❌ 不设 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: trueuseLazyFetch:数据初始为 undefined
  • server: false:SSR 时 dataundefined
  • API 可能返回空值时

default 必须是函数

default: () => [] ✅,default: [] ❌。

常见 default 值

ts
// 列表
default: () => []

// 单个对象
default: () => ({})

// 带默认字段的对象
default: () => ({ name: '', email: '', avatar: '/default.png' })

// 数字
default: () => 0

// null(允许为空但避免 undefined)
default: () => null

dedupe —— 去重策略

处理多个组件同时请求相同 key 的数据时,如何处理重复请求:

ts
// cancel:取消之前的请求(默认)
const { data } = await useFetch('/api/users', {
  dedupe: 'cancel',
})

// defer:等待之前的请求完成,共享结果
const { data } = await useFetch('/api/users', {
  dedupe: 'defer',
})

cancel vs defer

策略行为适用场景
cancel新请求到来时,取消上一个未完成的请求数据可能频繁变化的场景(如搜索)
defer如果已有相同 key 的请求在进行中,等待它完成并共享结果数据不常变化的场景(如配置、字典)

典型场景

ts
// 搜索场景:用 cancel(用户快速输入时,只保留最后一次搜索)
const { data } = await useFetch('/api/search', {
query: { q: keyword },
watch: [keyword],
dedupe: 'cancel',
})

// 配置场景:用 defer(多个组件需要同一份配置,共享请求)
const { data } = await useFetch('/api/config', {
dedupe: 'defer',
})

getCachedData —— 自定义缓存策略

完全控制何时使用缓存、何时重新获取:

ts
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,重新获取
  },
})

实战——基于时间的缓存

ts
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 是否使用深度响应式:

ts
// 浅 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 —— 请求超时

ts
const { data } = await useFetch('/api/slow', {
  timeout: 5000,  // 5 秒超时
})

INFO

超时行为:超时后 error ref 会被设置为超时错误 status 变为 'error'

响应式选项

所有 fetch 选项(queryparamsheaders 等)都支持响应式值:

ts
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

ts
const page = ref(1)

const { data } = await useFetch('/api/users', {
query: { page },
watch: [page],  // page 变化时重新请求
})

选项组合实战

搜索 + 分页

ts
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 }),
})

仅客户端 + 懒加载 + 默认值

ts
const { data, pending } = useLazyFetch('/api/me', {
  server: false,              // SSR 不请求
  default: () => ({ name: '' }), // 避免未定义报错
})

手动触发 + 条件请求

ts
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)
  }
}

知识脉络

text
useFetch → useAsyncData → $fetch → 懒加载获取

  └─→ 你在这里:数据获取选项

        ├─→ 相关:缓存与刷新(getCachedData / dedupe 详解)

        └─→ 相关:SSR 数据传递(server / Payload 详解)

基于 Nuxt 4 官方文档整理编写