Nuxt.js

状态管理

掌握 Nuxt 中的 useState 和 Pinia 集成,理解不同场景下如何选择状态管理方案。

🎯 引言

学会这篇文章,你能在 Nuxt 项目里合理地管理共享状态。你会掌握 useState 的用法,理解它和 Pinia 的分工,并知道在 SSR 场景下管理状态要注意什么。


🧱 为什么需要状态管理

当多个组件需要共享同一份数据时,如果只靠 props 传递和事件通知,代码会很快变得复杂。

状态管理的作用就是:把共享数据放到一个公共的地方,任何组件都能直接读取或修改。Nuxt 提供了两种常用方案:

方案适用场景复杂度
useState简单跨组件共享状态
Pinia大型应用、复杂业务逻辑
如果你已经学过 Pinia,在 Nuxt 里可以直接用;如果只是临时共享一个小状态,useState 更轻量。

🚀 useState:Nuxt 内置的轻量状态

useState 是 Nuxt 提供的一个组合式函数,用来创建响应式且支持 SSR 的共享状态

app/composables/useCounter.js
export function useCounter() {
    return useState('counter', () => 0);
}

在组件中使用:

app/pages/index.vue
<template>
    <div>
        <p>计数:{{ count }}</p>
        <button @click="count++">加一</button>
    </div>
</template>

<script setup>
const count = useCounter();
</script>
useState 第一个参数是全局唯一的 key,不同状态不能用相同的 key,否则数据会互相覆盖。

🔄 跨组件共享状态

因为 useState 通过 key 全局共享,所以多个组件使用同一个 key 时,读取到的是同一份数据。

app/components/AddButton.vue
<template>
    <button @click="count++">加一</button>
</template>

<script setup>
const count = useState('counter', () => 0);
</script>
app/components/ShowCount.vue
<template>
    <p>当前计数:{{ count }}</p>
</template>

<script setup>
const count = useState('counter', () => 0);
</script>

点击 AddButton 的按钮,ShowCount 里的数字会同步变化。

建议把 useState 封装到 composables/ 里的函数中,这样组件不需要关心 key,也不容易写错。

⚡ Pinia:复杂状态的首选

如果状态逻辑比较复杂,比如需要 actions、getters、模块化,建议用 Pinia。Nuxt 对 Pinia 有官方支持。

安装 Pinia:

npm install @pinia/nuxt pinia
当前环境 Node.js 为 v22.0.0,本课程对应 @pinia/nuxtpinia 均为较新版本,与 Nuxt ^3.16.0 兼容。

nuxt.config.ts 中添加模块:

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

创建 Store:

app/stores/user.js
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
    state: () => ({
        name: '游客',
        isLogin: false,
    }),
    actions: {
        login(name) {
            this.name = name;
            this.isLogin = true;
        },
    },
});

在组件中使用:

app/pages/index.vue
<template>
    <div>
        <p>用户名:{{ userStore.name }}</p>
        <button v-if="!userStore.isLogin" @click="userStore.login('小明')">登录</button>
    </div>
</template>

<script setup>
const userStore = useUserStore();
</script>
Pinia 的具体用法在 Pinia 课程中已经详细讲过,这里只展示如何在 Nuxt 中集成和使用。

🆚 useState 和 Pinia 怎么选

场景推荐方案
临时共享一个小状态,比如主题色、弹窗开关useState
需要 actions、getters、模块拆分Pinia
状态逻辑复杂,多个组件频繁读写Pinia
只想快速在几个组件间同步数据useState
不要为了让项目“更规范”而所有状态都用 Pinia。简单状态用 useState 可以减少样板代码。

🪤 SSR 下的状态注意点

useState 创建的状态会自动注入到服务端渲染的 HTML 中,然后客户端激活时复用。这意味着首屏就能看到正确状态,不会闪烁。

但要注意:不要在 useState 里放无法序列化的数据,比如函数、DOM 节点、循环引用对象等,否则 SSR 会报错。

// ❌ 错误:函数不能序列化
const fn = useState('fn', () => () => {});

// ✅ 正确:只放普通对象、数组、基础类型
const user = useState('user', () => ({ name: '小明', age: 18 }));

🧾 小节总结

  • Nuxt 中管理共享状态有 useState 和 Pinia 两种常用方案。
  • useState 适合简单状态,通过全局 key 共享,自动支持 SSR。
  • Pinia 适合复杂状态,通过 @pinia/nuxt 模块集成到 Nuxt 中。
  • 建议把 useState 封装到 composables/ 里,避免组件直接写 key。
  • SSR 场景下,useState 里的数据必须可序列化。

❓ 知识问答

Q1:useState 和 Vue 的 ref 有什么区别?

useState 内部就是 ref,但它额外支持 SSR 数据注入和跨组件共享。

Q2:两个组件使用同一个 useState key,数据会同步吗?

会。useState 通过 key 在全局维护同一份状态。

Q3:Nuxt 里必须用 Pinia 吗?

不是必须。简单项目用 useState 就够了,复杂项目再用 Pinia。

Q4:为什么 useState 里的数据要可序列化?

因为 SSR 时数据要从服务端传到客户端,不能序列化的数据无法安全传递。


🧪 小练习

完成一个主题切换功能:

  1. app/composables/useTheme.js 中用 useState 创建一个 theme 状态,默认值为 'light'
  2. 创建 app/components/ThemeToggle.vue,点击按钮在 'light''dark' 之间切换。
  3. 创建 app/components/ThemeDisplay.vue,显示当前主题。
  4. app/pages/index.vue 中同时使用这两个组件,验证状态同步。
app/composables/useTheme.js
export function useTheme() {
    // 请在这里创建 useState
}
app/components/ThemeToggle.vue
<template>
    <button @click="toggle">切换主题</button>
</template>

<script setup>
// 请在这里获取 theme 并实现切换
</script>

🎉 恭喜你已经掌握了 Nuxt 的状态管理!下一篇我们会学习 Nuxt 的配置与部署,理解 nuxt.config.ts 和不同渲染模式的选择。