nuxt-data
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNuxt 4 Data Management
Nuxt 4 数据管理
Composables, data fetching, and state management patterns for Nuxt 4 applications.
Nuxt 4 应用的组合式函数、数据获取及状态管理模式。
Quick Reference
快速参考
Data Fetching Methods
数据获取方法
| Method | Use Case | SSR | Caching | Reactive |
|---|---|---|---|---|
| Simple API calls | Yes | Yes | Yes |
| Custom async logic | Yes | Yes | Yes |
| Client-side only, events | No | No | No |
| 方法 | 适用场景 | SSR | Caching | Reactive |
|---|---|---|---|---|
| 简单API调用 | 是 | 是 | 是 |
| 自定义异步逻辑 | 是 | 是 | 是 |
| 仅客户端使用、事件触发 | 否 | 否 | 否 |
Composable Naming
组合式函数命名规范
| Prefix | Purpose | Example |
|---|---|---|
| State/logic composable | |
| Data fetching only | |
| 前缀 | 用途 | 示例 |
|---|---|---|
| 状态/逻辑组合式函数 | |
| 仅用于数据获取 | |
When to Load References
何时加载参考文档
Load when:
references/composables.md- Writing custom composables with complex state
- Debugging state management issues or memory leaks
- Implementing SSR-safe patterns with browser APIs
- Building authentication or complex state composables
- Understanding singleton vs per-call composable patterns
Load when:
references/data-fetching.md- Implementing API data fetching with reactive parameters
- Troubleshooting shallow vs deep reactivity issues
- Debugging data not refreshing when params change
- Implementing pagination, infinite scroll, or search
- Understanding transform functions, caching, or error handling
Load when:
references/pinia-integration.md- Setting up Pinia for complex state management
- Creating stores with getters and actions
- Integrating Pinia with SSR
- Persisting state across page reloads
当以下情况时加载 :
references/composables.md- 编写包含复杂状态的自定义组合式函数
- 调试状态管理问题或内存泄漏
- 结合浏览器API实现SSR安全模式
- 构建认证或复杂状态组合式函数
- 理解单例 vs 每次调用的组合式函数模式
当以下情况时加载 :
references/data-fetching.md- 实现带响应式参数的API数据获取
- 排查浅层 vs 深层响应式问题
- 调试参数变化时数据未刷新的问题
- 实现分页、无限滚动或搜索功能
- 理解转换函数、缓存或错误处理
当以下情况时加载 :
references/pinia-integration.md- 为复杂状态管理设置Pinia
- 创建包含getter和action的store
- 集成Pinia与SSR
- 在页面刷新后持久化状态
Composables
组合式函数
useState - The Foundation
useState - 基础
useStatetypescript
// composables/useCounter.ts
export const useCounter = () => {
// Singleton - shared across all components
const count = useState('counter', () => 0)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = 0
return { count, increment, decrement, reset }
}useStatetypescript
// 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 - Critical Distinction
useState vs ref - 关键区别
typescript
// CORRECT: Shared state (singleton pattern)
export const useAuth = () => {
const user = useState('auth-user', () => null) // Shared!
return { user }
}
// WRONG: Creates new instance every call!
export const useAuth = () => {
const user = ref(null) // Not shared!
return { user }
}Rule: Use for shared/global state. Use for local component state only.
useStatereftypescript
// 正确:共享状态(单例模式)
export const useAuth = () => {
const user = useState('auth-user', () => null) // 共享!
return { user }
}
// 错误:每次调用都会创建新实例!
export const useAuth = () => {
const user = ref(null) // 不共享!
return { user }
}规则:使用 管理共享/全局状态。仅在组件本地状态中使用 。
useStaterefComplete Authentication Composable
完整的认证组合式函数
typescript
// 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 // Skip on server
try {
const data = await $fetch('/api/auth/session')
user.value = data.user
} catch {
user.value = null
}
}
return { user, isAuthenticated, isLoading, login, logout, checkSession }
}typescript
// 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-Safe Browser APIs
SSR安全的浏览器API
typescript
// composables/useLocalStorage.ts
export const useLocalStorage = <T>(key: string, defaultValue: T) => {
const data = useState<T>(key, () => defaultValue)
// Only access localStorage on client
if (import.meta.client) {
const stored = localStorage.getItem(key)
if (stored) {
data.value = JSON.parse(stored)
}
// Watch and persist changes
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
}
return data
}typescript
// 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
}Data Fetching
数据获取
useFetch - Basic Usage
useFetch - 基础用法
typescript
// Simple GET request
const { data, error, pending, refresh } = await useFetch('/api/users')
// With options
const { data: users } = await useFetch('/api/users', {
method: 'GET',
query: { limit: 10, offset: 0 },
headers: { 'X-Custom-Header': 'value' }
})typescript
// 简单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' }
})Reactive Parameters
响应式参数
vue
<script setup lang="ts">
const page = ref(1)
const search = ref('')
// Auto-refetches when page or search changes
const { data: users, pending } = await useFetch('/api/users', {
query: {
page,
search,
limit: 10
}
})
// Or with computed
const query = computed(() => ({
page: page.value,
search: search.value,
limit: 10
}))
const { data } = await useFetch('/api/users', { query })
</script>vue
<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
}
})
// 或使用计算属性
const query = computed(() => ({
page: page.value,
search: search.value,
limit: 10
}))
const { data } = await useFetch('/api/users', { query })
</script>Transform Data
转换数据
typescript
const { data: userNames } = await useFetch('/api/users', {
transform: (users) => users.map(u => u.name)
})
// data.value is now string[] instead of User[]typescript
const { data: userNames } = await useFetch('/api/users', {
transform: (users) => users.map(u => u.name)
})
// 此时data.value为string[]类型,而非User[]Pick Specific Fields
选择特定字段
typescript
const { data } = await useFetch('/api/user', {
pick: ['id', 'name', 'email'] // Only these fields in payload
})typescript
const { data } = await useFetch('/api/user', {
pick: ['id', 'name', 'email'] // 仅保留这些字段在返回结果中
})useAsyncData - Custom Logic
useAsyncData - 自定义逻辑
typescript
// Multiple parallel requests
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 }
})
// Access: data.value.users, data.value.posts, data.value.statstypescript
// 多并行请求
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.statsError Handling
错误处理
typescript
const { data, error, status } = await useFetch('/api/users')
// Check error
if (error.value) {
console.error('Error:', error.value.message)
console.error('Status:', error.value.statusCode)
}
// Status values: 'idle' | 'pending' | 'success' | 'error'
if (status.value === 'error') {
showError(error.value)
}typescript
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)
}Manual Refresh
手动刷新
typescript
const { data, refresh, execute } = await useFetch('/api/users', {
immediate: false // Don't fetch on mount
})
// Fetch manually
await execute()
// Refresh (re-fetch)
await refresh()
// Refresh with new params
await refresh({ dedupe: true })typescript
const { data, refresh, execute } = await useFetch('/api/users', {
immediate: false // 挂载时不自动获取
})
// 手动触发获取
await execute()
// 刷新(重新获取)
await refresh()
// 使用新参数刷新
await refresh({ dedupe: true })Shallow vs Deep Reactivity (v4 Change)
浅层 vs 深层响应式(v4变更)
typescript
// Nuxt 4 default: Shallow reactivity
const { data } = await useFetch('/api/user')
data.value.name = 'New Name' // Won't trigger reactivity!
// Enable deep reactivity for mutations
const { data } = await useFetch('/api/user', {
deep: true
})
data.value.name = 'New Name' // Now works!
// Or refresh instead of mutating
const { data, refresh } = await useFetch('/api/user')
await $fetch('/api/user', { method: 'PATCH', body: { name: 'New Name' } })
await refresh() // Re-fetch updated datatypescript
// Nuxt 4 默认:浅层响应式
const { data } = await useFetch('/api/user')
data.value.name = 'New Name' // 不会触发响应式更新!
// 启用深层响应式以支持修改
const { data } = await useFetch('/api/user', {
deep: true
})
data.value.name = 'New Name' // 现在生效!
// 或修改后刷新数据
const { data, refresh } = await useFetch('/api/user')
await $fetch('/api/user', { method: 'PATCH', body: { name: 'New Name' } })
await refresh() // 重新获取更新后的数据Caching and Deduplication
缓存与去重
typescript
const { data } = await useFetch('/api/users', {
key: 'users-list', // Custom cache key
dedupe: 'cancel', // Cancel duplicate requests
getCachedData: (key, nuxtApp) => {
// Return cached data if valid
return nuxtApp.payload.data[key]
}
})typescript
const { data } = await useFetch('/api/users', {
key: 'users-list', // 自定义缓存键
dedupe: 'cancel', // 取消重复请求
getCachedData: (key, nuxtApp) => {
// 返回有效的缓存数据
return nuxtApp.payload.data[key]
}
})Lazy Loading Data
懒加载数据
typescript
// useLazyFetch - Navigation happens immediately, data loads in background
const { data, pending } = useLazyFetch('/api/users')
// useLazyAsyncData
const { data, pending } = useLazyAsyncData('users', () => $fetch('/api/users'))typescript
// useLazyFetch - 立即导航,后台加载数据
const { data, pending } = useLazyFetch('/api/users')
// useLazyAsyncData
const { data, pending } = useLazyAsyncData('users', () => $fetch('/api/users'))$fetch - Client-Side Only
$fetch - 仅客户端使用
typescript
// In event handlers (not during SSR)
const submitForm = async () => {
const result = await $fetch('/api/submit', {
method: 'POST',
body: formData.value
})
}
// In server routes
export default defineEventHandler(async (event) => {
const externalData = await $fetch('https://api.example.com/data')
return externalData
})typescript
// 在事件处理器中使用(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
})State Management
状态管理
useState Patterns
useState 模式
typescript
// Simple counter
const count = useState('count', () => 0)
// Complex object
const settings = useState('settings', () => ({
theme: 'light',
notifications: true,
language: 'en'
}))
// Typed state
interface User {
id: string
name: string
email: string
}
const user = useState<User | null>('user', () => null)typescript
// 简单计数器
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)Shared Cart Example
共享购物车示例
typescript
// 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 }
}typescript
// 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 Integration
Pinia 集成
bash
bun add pinia @pinia/nuxttypescript
// 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
}
}
})
// Usage in components
const authStore = useAuthStore()
await authStore.login('user@example.com', 'password')
console.log(authStore.userName)bash
bun add pinia @pinia/nuxttypescript
// 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)Common Anti-Patterns
常见反模式
Using ref Instead of useState
使用ref而非useState
typescript
// WRONG - Creates new instance every time!
export const useAuth = () => {
const user = ref(null) // Not shared
return { user }
}
// CORRECT
export const useAuth = () => {
const user = useState('auth-user', () => null)
return { user }
}typescript
// 错误 - 每次调用都会创建新实例!
export const useAuth = () => {
const user = ref(null) // 不共享
return { user }
}
// 正确
export const useAuth = () => {
const user = useState('auth-user', () => null)
return { user }
}Missing Error Handling
缺少错误处理
typescript
// WRONG
const { data } = await useFetch('/api/users')
console.log(data.value.length) // Crashes if error!
// CORRECT
const { data, error } = await useFetch('/api/users')
if (error.value) {
showToast({ type: 'error', message: error.value.message })
return
}
console.log(data.value.length)typescript
// 错误
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)Non-Deterministic Transform
非确定性转换
typescript
// WRONG - Causes hydration mismatch!
const { data } = await useFetch('/api/users', {
transform: (users) => users.sort(() => Math.random() - 0.5)
})
// CORRECT
const { data } = await useFetch('/api/users', {
transform: (users) => users.sort((a, b) => a.name.localeCompare(b.name))
})typescript
// 错误 - 会导致 hydration 不匹配!
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))
})Mutating Shallow Refs
修改浅层响应式引用
typescript
// WRONG - v4 uses shallow refs by default
const { data } = await useFetch('/api/user')
data.value.name = 'New Name' // Won't trigger reactivity!
// CORRECT - Option 1: Enable deep
const { data } = await useFetch('/api/user', { deep: true })
data.value.name = 'New Name'
// CORRECT - Option 2: Replace entire value
data.value = { ...data.value, name: 'New Name' }
// CORRECT - Option 3: Refresh after mutation
await $fetch('/api/user', { method: 'PATCH', body: { name: 'New Name' } })
await refresh()typescript
// 错误 - v4默认使用浅层响应式
const { data } = await useFetch('/api/user')
data.value.name = 'New Name' // 不会触发响应式更新!
// 正确 - 选项1:启用深层响应式
const { data } = await useFetch('/api/user', { deep: true })
data.value.name = 'New Name'
// 正确 - 选项2:替换整个值
data.value = { ...data.value, name: 'New Name' }
// 正确 - 选项3:修改后刷新数据
await $fetch('/api/user', { method: 'PATCH', body: { name: 'New Name' } })
await refresh()Troubleshooting
故障排除
Data Not Refreshing When Params Change:
- Ensure params are reactive: where
{ query: { page } }page = ref(1) - Check you're using the ref itself, not
.value
Hydration Mismatch with useState:
- Ensure key is unique:
useState('unique-key', () => value) - Avoid or
Math.random()in initial valuesDate.now()
State Lost on Navigation:
- Use instead of
useStatefor persistent stateref - Check you're using the same key across components
Infinite Refetch Loop:
- Check for reactive dependencies in transform function
- Use with
watchfor side effects{ immediate: false }
参数变化时数据未刷新:
- 确保参数是响应式的:其中
{ query: { page } }page = ref(1) - 检查是否直接使用ref本身,而非
.value
useState 导致Hydration不匹配:
- 确保键唯一:
useState('unique-key', () => value) - 避免在初始值中使用或
Math.random()Date.now()
导航时状态丢失:
- 使用而非
useState来保存持久化状态ref - 检查跨组件是否使用相同的键
无限重新获取循环:
- 检查转换函数中的响应式依赖
- 对副作用使用并设置
watch{ immediate: false }
Related Skills
相关技能
- nuxt-core: Project setup, routing, configuration
- nuxt-server: Server routes, API patterns
- nuxt-production: Performance, testing, deployment
Version: 4.0.0 | Last Updated: 2025-12-28 | License: MIT
- nuxt-core:项目搭建、路由、配置
- nuxt-server:服务端路由、API模式
- nuxt-production:性能、测试、部署
版本:4.0.0 | 最后更新:2025-12-28 | 许可证:MIT