Skip to content

useState

useState 是 Nuxt 提供的跨组件共享状态的组合式函数,SSR 安全。

为什么 Nuxt 需要专门的 useState?

在纯 Vue 项目中,你可能会用 ref 创建全局状态。但在 Nuxt 的 SSR 环境中,这有严重问题:

ts
// ❌ 危险!SSR 中的跨请求共享
// modules 级别的变量在所有请求之间共享
const currentUser = ref(null)

// 用户 A 访问 → currentUser 变成 Alice
// 用户 B 同时访问 → 可能看到 Alice 的数据!

useState 解决了这个问题:每个请求都有独立的状态实例,不会跨请求共享。

基本用法

ts
// 创建或获取共享状态
const count = useState('counter', () => 0)

// 读取
console.log(count.value)  // 0

// 修改
count.value++

useState 的工作原理

  1. 第一次调用 useState('counter', () => 0):创建状态,初始值为 0
  2. 后续调用 useState('counter', ...):获取已有状态(初始函数被忽略)
  3. 通过 key('counter')识别同一个状态

注意

第二个参数是初始化函数,只在状态首次创建时调用。如果状态已存在,这个函数不会执行。

跨组件共享

使用相同 key 的 useState 共享同一个状态:

vue
<!-- 组件 A -->
<script setup>
const count = useState('counter', () => 0)
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>
vue
<!-- 组件 B(另一个组件) -->
<script setup>
const count = useState('counter', () => 0)
</script>

<template>
  <p>当前计数:{{ count }}</p>
</template>

两个组件共享同一个 counter 状态。

为什么能共享?

因为 useState 的 key 是全局的。所有使用 useState('counter') 的组件,访问的都是同一个数据。

类型支持

ts
interface User {
  id: number
  name: string
  role: 'admin' | 'user'
}

// 带类型的 useState
const user = useState<User>('current-user')
user.value = { id: 1, name: 'Alice', role: 'admin' }

什么时候需要手动指定类型?

  • 初始值为 null 时(TypeScript 无法推断)
  • 初始值类型和最终类型不同时
  • 需要更精确的类型时

默认值

第二个参数是初始化函数,只在状态首次创建时调用:

ts
// 首次创建时初始化为空数组
const items = useState<string[]>('items', () => [])

// 首次创建时初始化为对象
const form = useState('form', () => ({
  name: '',
  email: '',
}))

为什么用函数而不是直接值?

ts
// ❌ 不推荐:每次调用都会创建新对象
useState('form', { name: '', email: '' })

// ✅ 推荐:只在首次创建时执行
useState('form', () => ({ name: '', email: '' }))

函数形式确保初始值只在需要时计算,避免不必要的对象创建。

封装为组合式函数

推荐将 useState 封装为组合式函数,避免 key 冲突:

ts
// app/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 }
}
ts
// app/composables/useAuth.ts
export const useAuth = () => {
  const user = useState<User | null>('auth-user', () => null)
  const isAuthenticated = computed(() => !!user.value)

  async function login(email: string, password: string) {
    user.value = await $fetch('/api/login', {
      method: 'POST',
      body: { email, password },
    })
  }

  function logout() {
    user.value = null
    const token = useCookie('auth-token')
    token.value = null
  }

  return { user, isAuthenticated, login, logout }
}

为什么要封装?

  1. 避免 key 冲突:所有使用 useCounter() 的地方共享同一个 key,不需要记住具体的 key 名
  2. 封装业务逻辑:状态 + 操作放在一起,更内聚
  3. 类型安全:组合式函数的返回值有完整类型
  4. 易于维护:改 key 只需要改一处

最佳实践

所有跨组件共享的状态都应该封装为组合式函数。组件内部的状态用 ref 就行。

clearNuxtState

清除指定 key 的状态:

ts
// 清除单个
clearNuxtState('counter')

// 清除多个
clearNuxtState(['counter', 'form'])

// 清除所有
clearNuxtState()

Nuxt 4.4+ 中,clearNuxtState 会将状态重置为初始值:

ts
const count = useState('counter', () => 0)
count.value = 42

clearNuxtState('counter')
// count.value 重置为 0(初始值),而非 undefined

什么时候用 clearNuxtState

  • 用户登出时清除所有用户相关状态
  • 测试中重置状态
  • 调试时清除缓存

useState vs ref

特性useStateref
SSR 安全❌(可能跨请求共享)
跨组件共享✅ 通过 key❌ 独立实例
服务端传递✅ 自动
适用场景全局共享状态组件内部状态

SSR 跨请求问题

ts
// ❌ 使用 ref,多用户共享同一个变量
const sharedData = ref({})

export default defineEventHandler(() => {
  sharedData.value = { user: 'Alice' }  // 用户 B 也看到这个数据!
  return sharedData.value
})

// ✅ 使用 useState,每次请求独立
const data = useState('request-data', () => ({}))

为什么 ref 不安全?

Node.js 是单进程的,模块级变量在所有请求之间共享。用户 A 和用户 B 的请求可能在同一个进程中处理,如果用 ref 存储用户数据,A 可能看到 B 的数据。

useState 通过 Nuxt 的请求上下文机制,确保每个请求有独立的状态。

什么时候用 useState,什么时候用 Pinia?

场景useStatePinia
简单的全局状态✅ 足够也能用
需要 getters/actions
需要插件(持久化、DevTools)
大型应用的状态管理可能不够✅ 推荐
快速原型开发✅ 简单直接需要额外安装

选择建议

  • 小项目/简单状态 → useState
  • 大项目/复杂状态 → Pinia
  • 不确定 → 先用 useState,复杂了再迁移到 Pinia(迁移很简单)

注意事项

  1. key 要唯一:避免不同状态使用相同 key
  2. 初始化函数:默认值使用函数而非直接值
  3. SSR 安全:所有跨组件共享的状态都应该用 useState
  4. 不要存储不可序列化的值:函数、DOM 元素等无法在 SSR Payload 中传递
  5. 不要在 key 中使用动态值:如 useState('user-' + id),这会导致 key 管理混乱

知识脉络

text
数据获取 → 你在这里:useState

              ├─→ 下一步:Pinia 集成

              └─→ 相关:SSR 数据传递(06-数据获取)

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