Nuxt4数据管理 nuxt-data

这个技能用于Nuxt 4框架中的数据管理,涵盖composables、数据获取和状态管理。它帮助开发者处理响应式数据、SSR兼容性和复杂状态逻辑,适用于前端开发场景。关键词:Nuxt, 数据管理, composables, useFetch, useState, Pinia, SSR, 响应式编程。

前端开发 0 次安装 0 次浏览 更新于 3/7/2026

名称: nuxt-data 描述: | Nuxt 4 数据管理:composables、使用useFetch/useAsyncData进行数据获取, 以及使用useState和Pinia进行状态管理。

使用时机:创建自定义composables、使用useFetch或useAsyncData获取数据、 使用useState管理全局状态、集成Pinia、调试响应式数据问题、 或实现SSR安全的状态模式。

关键词:useFetch, useAsyncData, $fetch, useState, composables, Pinia, 数据获取, 状态管理, 响应式, 浅响应式, 响应式键, transform, pending, error, refresh, dedupe, caching 许可证: MIT 元数据: 版本: 4.0.0 作者: Claude Skills Maintainers 类别: 框架 框架: Nuxt 框架版本: 4.x 最后验证: 2025-12-28

Nuxt 4 数据管理

Nuxt 4 应用程序的Composables、数据获取和状态管理模式。

快速参考

数据获取方法

方法 使用场景 SSR 缓存 响应式
useFetch 简单API调用
useAsyncData 自定义异步逻辑
$fetch 仅客户端,事件

Composable 命名

前缀 目的 示例
use 状态/逻辑composable useAuth, useCart
fetch 仅数据获取 fetchUsers(罕见)

何时加载参考

加载 references/composables.md 当:

  • 编写具有复杂状态的自定义composables
  • 调试状态管理问题或内存泄漏
  • 实现使用浏览器API的SSR安全模式
  • 构建认证或复杂状态composables
  • 理解单例与每次调用composable模式

加载 references/data-fetching.md 当:

  • 实现具有响应式参数的API数据获取
  • 排除浅响应式与深响应式问题
  • 调试参数更改时数据不刷新的问题
  • 实现分页、无限滚动或搜索
  • 理解transform函数、缓存或错误处理

加载 references/pinia-integration.md 当:

  • 为复杂状态管理设置Pinia
  • 创建具有getters和actions的stores
  • 将Pinia与SSR集成
  • 持久化状态跨页面重载

Composables

useState - 基础

useState 创建SSR安全、共享的响应式状态,跨组件实例持久化。

// composables/useCounter.ts
export const useCounter = () => {
  // 单例 - 在所有组件间共享
  const count = useState('counter', () => 0)

  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = 0

  return { count, increment, decrement, reset }
}

useState vs ref - 关键区别

// 正确:共享状态(单例模式)
export const useAuth = () => {
  const user = useState('auth-user', () => null)  // 共享!
  return { user }
}

// 错误:每次调用创建新实例!
export const useAuth = () => {
  const user = ref(null)  // 不共享!
  return { user }
}

规则:使用 useState 用于共享/全局状态。仅使用 ref 用于本地组件状态。

完整认证Composable

// composables/useAuth.ts
export const useAuth = () => {
  const user = useState<User | null>('auth-user', () => null)
  const isAuthenticated = computed(() => !!user.value)
  const isLoading = useState('auth-loading', () => false)

  const login = async (email: string, password: string) => {
    isLoading.value = true
    try {
      const data = await $fetch('/api/auth/login', {
        method: 'POST',
        body: { email, password }
      })
      user.value = data.user
      return { success: true }
    } catch (error) {
      return { success: false, error: error.message }
    } finally {
      isLoading.value = false
    }
  }

  const logout = async () => {
    await $fetch('/api/auth/logout', { method: 'POST' })
    user.value = null
    navigateTo('/login')
  }

  const checkSession = async () => {
    if (import.meta.server) return  // 在服务器上跳过
    try {
      const data = await $fetch('/api/auth/session')
      user.value = data.user
    } catch {
      user.value = null
    }
  }

  return { user, isAuthenticated, isLoading, login, logout, checkSession }
}

SSR安全浏览器API

// composables/useLocalStorage.ts
export const useLocalStorage = <T>(key: string, defaultValue: T) => {
  const data = useState<T>(key, () => defaultValue)

  // 仅在客户端访问localStorage
  if (import.meta.client) {
    const stored = localStorage.getItem(key)
    if (stored) {
      data.value = JSON.parse(stored)
    }

    // 监视并持久化更改
    watch(data, (newValue) => {
      localStorage.setItem(key, JSON.stringify(newValue))
    }, { deep: true })
  }

  return data
}

数据获取

useFetch - 基本用法

// 简单GET请求
const { data, error, pending, refresh } = await useFetch('/api/users')

// 带选项
const { data: users } = await useFetch('/api/users', {
  method: 'GET',
  query: { limit: 10, offset: 0 },
  headers: { 'X-Custom-Header': 'value' }
})

响应式参数

<script setup lang="ts">
const page = ref(1)
const search = ref('')

// 当page或search更改时自动重新获取
const { data: users, pending } = await useFetch('/api/users', {
  query: {
    page,
    search,
    limit: 10
  }
})

// 或使用computed
const query = computed(() => ({
  page: page.value,
  search: search.value,
  limit: 10
}))

const { data } = await useFetch('/api/users', { query })
</script>

转换数据

const { data: userNames } = await useFetch('/api/users', {
  transform: (users) => users.map(u => u.name)
})

// data.value 现在是 string[] 而不是 User[]

选择特定字段

const { data } = await useFetch('/api/user', {
  pick: ['id', 'name', 'email']  // 仅这些字段在有效载荷中
})

useAsyncData - 自定义逻辑

// 多个并行请求
const { data } = await useAsyncData('dashboard', async () => {
  const [users, posts, stats] = await Promise.all([
    $fetch('/api/users'),
    $fetch('/api/posts'),
    $fetch('/api/stats')
  ])
  return { users, posts, stats }
})

// 访问:data.value.users, data.value.posts, data.value.stats

错误处理

const { data, error, status } = await useFetch('/api/users')

// 检查错误
if (error.value) {
  console.error('错误:', error.value.message)
  console.error('状态:', error.value.statusCode)
}

// 状态值:'idle' | 'pending' | 'success' | 'error'
if (status.value === 'error') {
  showError(error.value)
}

手动刷新

const { data, refresh, execute } = await useFetch('/api/users', {
  immediate: false  // 不挂载时获取
})

// 手动获取
await execute()

// 刷新(重新获取)
await refresh()

// 刷新带新参数
await refresh({ dedupe: true })

浅响应式 vs 深响应式(v4更改)

// Nuxt 4 默认:浅响应式
const { data } = await useFetch('/api/user')
data.value.name = '新名称'  // 不会触发响应式!

// 启用深响应式以进行突变
const { data } = await useFetch('/api/user', {
  deep: true
})
data.value.name = '新名称'  // 现在有效!

// 或刷新而不是突变
const { data, refresh } = await useFetch('/api/user')
await $fetch('/api/user', { method: 'PATCH', body: { name: '新名称' } })
await refresh()  // 重新获取更新数据

缓存和去重

const { data } = await useFetch('/api/users', {
  key: 'users-list',           // 自定义缓存键
  dedupe: 'cancel',            // 取消重复请求
  getCachedData: (key, nuxtApp) => {
    // 如果有效返回缓存数据
    return nuxtApp.payload.data[key]
  }
})

懒加载数据

// useLazyFetch - 导航立即发生,数据在后台加载
const { data, pending } = useLazyFetch('/api/users')

// useLazyAsyncData
const { data, pending } = useLazyAsyncData('users', () => $fetch('/api/users'))

$fetch - 仅客户端

// 在事件处理程序中(不在SSR期间)
const submitForm = async () => {
  const result = await $fetch('/api/submit', {
    method: 'POST',
    body: formData.value
  })
}

// 在服务器路由中
export default defineEventHandler(async (event) => {
  const externalData = await $fetch('https://api.example.com/data')
  return externalData
})

状态管理

useState 模式

// 简单计数器
const count = useState('count', () => 0)

// 复杂对象
const settings = useState('settings', () => ({
  theme: 'light',
  notifications: true,
  language: 'en'
}))

// 类型化状态
interface User {
  id: string
  name: string
  email: string
}
const user = useState<User | null>('user', () => null)

共享购物车示例

// composables/useCart.ts
interface CartItem {
  id: string
  name: string
  price: number
  quantity: number
}

export const useCart = () => {
  const items = useState<CartItem[]>('cart-items', () => [])

  const total = computed(() =>
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )

  const itemCount = computed(() =>
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )

  const addItem = (product: Omit<CartItem, 'quantity'>) => {
    const existing = items.value.find(i => i.id === product.id)
    if (existing) {
      existing.quantity++
    } else {
      items.value.push({ ...product, quantity: 1 })
    }
  }

  const removeItem = (id: string) => {
    items.value = items.value.filter(i => i.id !== id)
  }

  const updateQuantity = (id: string, quantity: number) => {
    const item = items.value.find(i => i.id === id)
    if (item) {
      item.quantity = Math.max(0, quantity)
      if (item.quantity === 0) removeItem(id)
    }
  }

  const clearCart = () => {
    items.value = []
  }

  return { items, total, itemCount, addItem, removeItem, updateQuantity, clearCart }
}

Pinia 集成

bun add pinia @pinia/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt']
})

// stores/auth.ts
import { defineStore } from 'pinia'

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: null as User | null,
    token: null as string | null
  }),

  getters: {
    isAuthenticated: (state) => !!state.user,
    userName: (state) => state.user?.name ?? 'Guest'
  },

  actions: {
    async login(email: string, password: string) {
      const { user, token } = await $fetch('/api/auth/login', {
        method: 'POST',
        body: { email, password }
      })
      this.user = user
      this.token = token
    },

    logout() {
      this.user = null
      this.token = null
    }
  }
})

// 在组件中使用
const authStore = useAuthStore()
await authStore.login('user@example.com', 'password')
console.log(authStore.userName)

常见反模式

使用 ref 代替 useState

// 错误 - 每次创建新实例!
export const useAuth = () => {
  const user = ref(null)  // 不共享
  return { user }
}

// 正确
export const useAuth = () => {
  const user = useState('auth-user', () => null)
  return { user }
}

缺少错误处理

// 错误
const { data } = await useFetch('/api/users')
console.log(data.value.length)  // 如果错误则崩溃!

// 正确
const { data, error } = await useFetch('/api/users')
if (error.value) {
  showToast({ type: 'error', message: error.value.message })
  return
}
console.log(data.value.length)

非确定性转换

// 错误 - 导致水合不匹配!
const { data } = await useFetch('/api/users', {
  transform: (users) => users.sort(() => Math.random() - 0.5)
})

// 正确
const { data } = await useFetch('/api/users', {
  transform: (users) => users.sort((a, b) => a.name.localeCompare(b.name))
})

突变浅引用

// 错误 - v4 默认使用浅引用
const { data } = await useFetch('/api/user')
data.value.name = '新名称'  // 不会触发响应式!

// 正确 - 选项1:启用深响应式
const { data } = await useFetch('/api/user', { deep: true })
data.value.name = '新名称'

// 正确 - 选项2:替换整个值
data.value = { ...data.value, name: '新名称' }

// 正确 - 选项3:突变后刷新
await $fetch('/api/user', { method: 'PATCH', body: { name: '新名称' } })
await refresh()

故障排除

参数更改时数据不刷新:

  • 确保参数是响应式的:{ query: { page } } 其中 page = ref(1)
  • 检查您使用的是ref本身,而不是 .value

与 useState 的水合不匹配:

  • 确保键是唯一的:useState('unique-key', () => value)
  • 避免在初始值中使用 Math.random()Date.now()

导航时状态丢失:

  • 使用 useState 代替 ref 用于持久状态
  • 检查跨组件使用相同键

无限重新获取循环:

  • 检查transform函数中的响应式依赖项
  • 对副作用使用 watch{ immediate: false }

相关技能

  • nuxt-core:项目设置、路由、配置
  • nuxt-server:服务器路由、API模式
  • nuxt-production:性能、测试、部署

版本: 4.0.0 | 最后更新: 2025-12-28 | 许可证: MIT