Nuxt4 状态管理
状态管理解决组件间共享数据的问题,比如用户登录状态、购物车内容,多个组件都要读写同一份数据。
Nuxt 提供两种方案,useState 适合简单全局状态,Pinia 适合复杂应用状态。
useState 基础用法
useState 是 Nuxt 内置的 SSR 友好的响应式状态管理:
<script setup>
// 第一个参数是 key,第二个参数是初始值工厂函数
const count = useState('count', () => 0)
function increment() {
count.value++
}
</script>
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="increment">加一</button>
</div>
</template>
在另一个组件里用相同的 key 就能读写同一份数据:
<!-- app/components/CountDisplay.vue -->
<script setup>
const count = useState('count') // 同一个 key,同一份数据
</script>
<template>
<p>当前计数:{{ count }}</p>
</template>
SSR 数据同步
useState 和普通 ref 最大的区别是 SSR 兼容。服务端渲染时状态存在服务端,hydration 时 Nuxt 会把服务端的值序列化到页面中,客户端直接复用,两端数据一致。
如果用普通 ref 存全局状态,服务端和客户端各有一份,会出现水合不一致的警告。
注意事项
| 注意点 | 说明 |
|---|---|
| key 全局唯一 | 不同功能用不同 key,避免数据串用 |
| 不在异步回调里初始化 | 初始值工厂函数里不能调用 useState,只能初始化数据 |
| 建议封装成 composable | 把 useState 封装进 composables/,自动导入随处可用 |
推荐的封装方式:
// app/composables/useAuth.ts
export function useAuth() {
return useState('auth', () => ({ loggedIn: false, user: null }))
}
<script setup>
const auth = useAuth()
</script>
Pinia 集成
Pinia 是 Vue 官方推荐的状态管理库,Nuxt 提供了 @pinia/nuxt 模块无缝集成。适合复杂状态,比如购物车、多模块数据、带 getters 和 actions 的状态。
安装
npm install @pinia/nuxt pinia
在 nuxt.config.ts 中注册模块:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
})
定义 store
// app/stores/cart.ts
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
getters: {
// 计算总价,有缓存
totalPrice: (state) => state.items.reduce((sum, item) => sum + item.price, 0)
},
actions: {
addItem(item) {
this.items.push(item)
},
removeItem(id) {
this.items = this.items.filter(item => item.id !== id)
}
}
})
在组件中使用
<script setup>
const cart = useCartStore()
function handleAdd() {
cart.addItem({ id: 1, name: '商品', price: 99 })
}
</script>
<template>
<div>
<p>商品数量:{{ cart.items.length }}</p>
<p>总价:{{ cart.totalPrice }}</p>
<button @click="handleAdd">加入购物车</button>
</div>
</template>
注意:store 中解构出的 state 会丢失响应性,需要响应式数据时用 storeToRefs:
<script setup>
import { storeToRefs } from 'pinia'
const cart = useCartStore()
const { items, totalPrice } = storeToRefs(cart) // 保持响应式
</script>
useCartStore 在 Nuxt 中也会自动导入(app/stores/ 目录默认被 Pinia 模块扫描),不需要手动 import。
两种方案对比
| 对比 | useState | Pinia |
|---|---|---|
| 上手难度 | 极低,一行代码 | 中等,需要理解 store 概念 |
| 适用场景 | 简单全局状态(登录态、主题、语言) | 复杂状态(购物车、多模块数据) |
| 数据组织 | 零散,靠 key 区分 | 按 store 模块化组织 |
| 调试工具 | 无 | DevTools 支持,可调试时间线 |
| SSR 支持 | 内置 | 模块内置,开箱即用 |
| 数据持久化 | 无 | 配合插件可实现 localStorage 持久化 |
组合建议
实际项目经常两者搭配使用,简单的登录状态用 useState,复杂业务数据用 Pinia:
// app/composables/useUser.ts
export function useUser() {
const user = useState('user', () => null) // 简单状态直接用 useState
async function fetchUser() {
const { data } = await useFetch('/api/user')
user.value = data.value
}
return { user, fetchUser }
}
一句话总结:简单全局状态用 useState,key 相同数据共享,SSR 自动同步。复杂业务状态用 Pinia,模块化组织数据,DevTools 可调试。