首页 / Nuxt 4 入门教程 / 状态管理之 Pinia

Nuxt 4 入门教程

状态管理之 Pinia

本教程共 50 篇 · 第 26 篇 · 更新于 2026-08-08 · 约 7 分钟阅读

NuxtNuxt4Pinia状态管理store

本节目标:学会在 Nuxt 项目里装好 Pinia,定义自己的 store,并在组件中读写共享状态。

26-1

随着应用变大,你会发现:多个组件要共享同一份数据(比如当前登录用户、购物车),还要有一套统一的「改数据」的方法。如果靠一层层传 props,代码会乱成一团。

这时候就该上「状态管理库」。Vue 官方推荐的是 Pinia——它是 Vue 生态里最主流的状态管理方案,Nuxt 也对它做了深度集成。简单说,store(仓库) 就是一个集中存放状态和改状态方法的容器,谁都能读、谁都能改,而且改完大家都看得到。

Note

Nuxt 4 之前的老版本用过 Vuex,但现在已经不推荐了。新项目直接用 Pinia,它是 Vue 官方钦定的方案,和 Nuxt 配合也最顺。

26-2

Nuxt 用「模块」来扩展能力,Pinia 就是一个官方模块。装它只需一条命令,在 Nuxt 4 里直接指向 app/ 目录结构也没问题:

npx nuxt module add pinia

这条命令会做两件事:把 @pinia/nuxt 加进依赖,并在 nuxt.config.ts 里自动登记模块。你一般不需要手动改配置,装完即可用。

Tip

如果命令没法自动跑,也可以手动 npm i @pinia/nuxt,然后在 nuxt.config.tsmodules 数组里加上 '@pinia/nuxt'。新项目推荐用 npx nuxt module add,省心且不易出错。

26-3

Pinia 里用 defineStore 来定义一个 store。在 Nuxt 4 的约定下,store 文件放在 app/stores/ 目录,文件名随意,框架会自动导入。

store 有两种常见写法:选项式(state / actions / getters 分开写)和函数式(setup store)。初学者用选项式最直观:

export const useWebsiteStore = defineStore('websiteStore', {
  // state:数据源,必须是个返回对象的函数
  state: () => ({
    name: '',
    description: '',
  }),
  // actions:改状态的方法,可以是异步
  actions: {
    async fetch () {
      const infos = await $fetch('https://api.nuxt.com/modules/pinia')
      this.name = infos.name
      this.description = infos.description
    },
  },
})

这里 defineStore 的第一个参数 'websiteStore' 是这个仓库的唯一 id,第二个参数是配置。state 是数据本体,actions 是修改数据、发起请求等方法。注意 state 必须写成「返回对象的函数」,不能是个普通对象字面量。

26-4

store 用起来像调用一个函数。在组件里:

<script setup lang="ts">
// 取得 store 实例
const website = useWebsiteStore()

// 调用 action 去取数据(只跑一次)
await callOnce(website.fetch)
</script>

<template>
  <main>
    <h1>{{ website.name }}</h1>
    <p>{{ website.description }}</p>
  </main>
</template>

读状态直接 website.name;改状态可以直接赋值(如 website.name = '新名'),也可以调用 action。因为 Pinia 的状态是响应式的,模板会自动跟着更新。

Note

这里用了 callOnce 而不是把 fetch 直接塞进 useAsyncData。因为 useAsyncData 定位是「取数+缓存」,用来触发 Pinia 的副作用 action 容易反复执行。初始化性质的副作用,用 callOnce 更稳妥(详见第 23 章)。

26-5

除了选项式,也可以用更接近组合式 API 的「函数式 store」,把 refcomputed 直接当状态和计算值:

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const double = computed(() => count.value * 2)

  function increment () {
    count.value++
  }

  return { count, double, increment }
})

组件里用法完全一样:const counter = useCounterStore(),然后 counter.countcounter.increment()。两种写法你可以按喜好选,功能等价。

26-6

有些值不是「存起来」的,而是「由现有状态算出来」的,比如购物车里商品的总价。Pinia 里用 getters 表达这种派生数据,它类似 Vue 的 computed,会随依赖自动更新。

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] as { name: string; price: number; qty: number }[],
  }),
  getters: {
    total (state) {
      return state.items.reduce((sum, i) => sum + i.price * i.qty, 0)
    },
  },
})

组件里直接读 cart.total 就行,不用自己写求和逻辑,也不会因为多处计算口径不一致而出错。状态变,总价自动跟着变。

26-7

如果你想让 store 在任何地方都能「开箱即用」,甚至把它挂到 Nuxt 应用实例上,可以写一个客户端/服务端插件(app/plugins/)。最常见的需求是:在应用启动初期就初始化好某个全局 store。

export default defineNuxtPlugin((nuxtApp) => {
  // 在应用层面做一次性初始化,例如预取用户资料
  const user = useUserStore()
  // 注意:真正发请求建议配合 callOnce,避免重复执行
})
Note

在插件里调用 store 是允许的,但插件会在每次请求(服务端)和每次加载(客户端)都跑,所以里面涉及「取数」的动作务必用 callOnce 或条件判断包好,别让它在每个请求里重复触发。

26-8

Pinia 的强项就是「全局共享」。无论你在 A 组件还是 B 组件调用 useWebsiteStore(),拿到的都是同一个 store 实例。A 里改了 name,B 里立刻看得见。

<script setup lang="ts">
const website = useWebsiteStore()
</script>

<template>
  <aside>{{ website.name }}</aside>
</template>

这让「登录用户信息」「主题设置」「购物车」这类需要跨页面共享的数据有了稳妥的归宿。

26-9

Pinia 在 Nuxt 里是「SSR 友好」的:服务器渲染时它会为每个请求创建独立的 store 实例,并随 payload 把状态传到浏览器,水合时复用,不会串号。

Warning

正因为服务器是每个请求一个独立实例,你绝不能把 store 定义成「模块顶层的一个全局单例对象」然后到处 import。一定要通过 useXxxStore() 函数去获取实例,让 Nuxt 替你管理每个请求的隔离。这正是 Pinia 在 Nuxt 中的标准用法,框架已经帮你处理好了。

26-10

Pinia 模块的用法在 Nuxt 3 与 Nuxt 4 一致。仅目录位置不同:Nuxt 4 的 store 放在 app/stores/,Nuxt 3 放在根目录 stores/defineStoreuseXxxStore() 的写法不变。

26-11

Pinia 是 Nuxt 管理复杂共享状态的标配。记住三步:用 npx nuxt module add pinia 安装;在 app/stores/ 里用 defineStore 定义 state 和 actions;在组件里调用 useXxxStore() 读写。它是处理「跨组件、有改法、需持久」数据的主力。下一章我们看 Nuxt 自带的轻量级方案 useState,适合更简单的共享场景。

26-8 Pinia 在 SSR 中的注意事项

Pinia 在纯前端项目里使用非常直接,但在 Nuxt 的 SSR 环境下有一些额外注意事项。最关键的一点是:服务端渲染时,所有请求共享同一个 Node.js 进程。如果 Pinia store 的数据在不同请求间泄漏,用户 A 可能会看到用户 B 的数据。

Nuxt 通过 useState 解决了这个问题。当你在 Pinia store 里用 useState 管理状态时,Nuxt 会确保每个请求有独立的状态副本,不会互相干扰。这也是为什么在 Nuxt 项目里推荐使用 useState 而不是 Pinia 原生的 ref 来定义 store 状态。

另外,Pinia 的持久化插件(如 pinia-plugin-persistedstate)在 SSR 环境下需要谨慎使用。localStorage 只在客户端存在,服务端访问会报错。建议把持久化逻辑限制在客户端执行,或者使用 Nuxt 提供的 useCookie 作为替代方案。

26-9 何时该用 Pinia 何时该用 useState

Nuxt 提供了 useState 作为内置的状态管理方案,同时支持 Pinia 作为第三方状态管理库。两者各有适用场景。

useState 适合管理简单的、页面级别的状态。比如一个表单的临时数据、一个开关的布尔值、一个列表的筛选条件。它的 API 简单直接,不需要额外安装依赖。

Pinia 适合管理复杂的、跨页面共享的状态。比如用户登录信息、购物车内容、全局配置。Pinia 提供了更丰富的功能:模块化组织、开发者工具支持、持久化插件等。当状态逻辑变得复杂时,Pinia 的结构化优势就体现出来了。