状态管理之 Pinia
本教程共 50 篇 · 第 26 篇 · 更新于 2026-08-08 · 约 7 分钟阅读
本节目标:学会在 Nuxt 项目里装好 Pinia,定义自己的 store,并在组件中读写共享状态。
26-1
随着应用变大,你会发现:多个组件要共享同一份数据(比如当前登录用户、购物车),还要有一套统一的「改数据」的方法。如果靠一层层传 props,代码会乱成一团。
这时候就该上「状态管理库」。Vue 官方推荐的是 Pinia——它是 Vue 生态里最主流的状态管理方案,Nuxt 也对它做了深度集成。简单说,store(仓库) 就是一个集中存放状态和改状态方法的容器,谁都能读、谁都能改,而且改完大家都看得到。
NoteNuxt 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.ts的modules数组里加上'@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」,把 ref、computed 直接当状态和计算值:
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.count、counter.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/。defineStore、useXxxStore() 的写法不变。
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 的结构化优势就体现出来了。