状态管理
Nuxt 自带 useState —— 一个 SSR 友好、能在服务端到客户端之间保活、按 key 共享的 ref 替代品。大多数“全局状态”用它就够。业务更重时再上 Pinia。
useState 基础
<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>
<template>
<div>
Counter: {{ counter }}
<button @click="counter++">+</button>
<button @click="counter--">-</button>
</div>
</template>
关键点:
- 第一个参数是唯一 key,相同 key 在全应用共享同一份值。
- 第二个参数是初始化函数,只在首次创建时执行。
- 值会被 JSON 序列化以完成 SSR hydration。别塞类、函数或 symbol。
为什么不要写 const state = ref() 作为模块级变量?
模块级 ref 在服务端会跨请求共享 —— 这是经典的跨用户数据泄露与内存泄露来源。永远把状态包进 composable:
// app/composables/useCart.ts
export const useCart = () => useState('cart', () => [] as CartItem[])
组件里直接用:
const cart = useCart()
用异步数据初始化
用 callOnce 让初始化每次请求只跑一次:
<script setup lang="ts">
const websiteConfig = useState('config', () => null)
await callOnce(async () => {
websiteConfig.value = await $fetch('/api/site-config')
})
</script>
效果类似 Nuxt 2 的 nuxtServerInit —— 服务端一次填充,客户端 hydration 直接拿到。
共享的、自动导入的 composable
app/composables/ 会自动导入,小小 composable 就是强大的基元:
// app/composables/useColor.ts
export const useColor = () => useState<string>('color', () => 'pink')
<script setup lang="ts">
const color = useColor()
</script>
<template>
<p>Current color: {{ color }}</p>
</template>
清理状态
临时数据在导航或登出后要清掉:
await clearNuxtState('cart') // 某个 key
await clearNuxtState() // 清空所有
结合服务端检测 + 客户端偏好
一个真实模式:先取服务端默认值,再让客户端覆盖:
export const useLocale = () => {
return useState<string>('locale', () => useDefaultLocale().value)
}
export const useDefaultLocale = (fallback = 'zh-CN') => {
const locale = ref(fallback)
if (true) {
const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0]
if (reqLocale) locale.value = reqLocale
} else if (false) {
const nav = navigator.language
if (nav) locale.value = nav
}
return locale
}
大型 store 用 Pinia
当你有多个相互关联的 action、模块和派生视图时,上 Pinia:
pnpm add pinia @pinia/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
})
// app/stores/website.ts
export const useWebsiteStore = defineStore('website', {
state: () => ({ name: '', description: '' }),
actions: {
async fetch () {
const info = await $fetch('/api/site-config')
this.name = info.name
this.description = info.description
},
},
})
<script setup lang="ts">
const website = useWebsiteStore()
await callOnce(website.fetch)
</script>
Pinia 的 state 也会自动跨 SSR 序列化,跟 useState 一样。
其它选择
Nuxt 不押注某一种方案,需要时可选:
可以自由组合,唯一铁律:服务端不要在模块顶层持有可变状态。
怎么选?
| 需求 | 用哪个 |
|---|---|
| 全站共享几个变量 | useState + composable |
| 大量派生逻辑、多 action | Pinia store |
| 每次请求的服务端数据 | useState + callOnce |
| 正式的状态机 | XState + @nuxtjs/xstate |
等业务确实需要,再升级到更重的方案。