跳到主要内容

创建日期:2026-09-08 | 最近更新:2026-09-08 本系列以 pinia 4.0.3 + vue 3.5.42 为准(本机 Node 环境实测跑通;带 [实测] 标记的输出均为真实运行)。它是本站 Vue3 系列的配套——讲 Vue 3 状态管理库 Pinia 从入门到精通。

Pinia 入门:为什么取代 Vuex,以及第一个 store

一句话:Pinia 是 Vue 官方钦定的状态管理库(Vuex 5 就是 Pinia)。它把「数据(state)+ 派生(getters)+ 改数据的方法(actions)」整成一个一个 store,任何组件都能拿。和 Vuex 最大的不同:没有 mutation、改 state 就是直接赋值,TypeScript 推导也是开箱即用。本系列用大量可跑代码把 state / getters / actions、组合式 store、订阅、插件、工程化、原理一路讲完。

1. 为什么是 Pinia(而不是 Vuex)

对比Vuex 4Pinia(本系列)
改状态必须 commit(mutation) 走流程直接改 store.count++($patch 只是批量/调试工具)
派生值getters,用模块路径字符串取getters,但直接 store.double,天然响应式
TypeScript推导费劲,常要手写类型基于字面量/函数定义自动推导,几乎零样板
模块组织modules 嵌套、命名空间痛苦store 扁平、按文件即按 store,import 即用
Devtools有,且 vue-devtools 里能直接看/改 state
Vue 2曾并行支持只服务 Vue 3(本机 vue 3.5)

核心心智变化一句话:「改状态 = 直接赋值」。mutation 那层仪式感被干掉了——因为它并不能真的保证单向数据流,反而把简单的事变复杂。

2. 装与挂(两步)

npm i pinia
// main.js —— 关键就一行 app.use(pinia)
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const pinia = createPinia();
createApp(App).use(pinia).mount('#app');

app.use(pinia) 这一步不是仪式——它执行 pinia.install(),把实例挂到 app 上并触发所有 pinia 插件(本系列插件篇会实测:不 app.usepinia.use(...) 注册的插件根本不会跑,v4 会把它们先暂存在队列里)。没有组件树也行,测试里我们用一个空 createApp({}).use(pinia) 就够。

3. 第一个 store:counter

约定一个 store 一个文件。最简单的 options 写法(后面篇 2再讲组合式):

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

export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'counter' }), // 注意必须是函数,返回新对象
getters: {
double: (state) => state.count * 2,
},
actions: {
inc(by = 1) {
this.count += by; // 直接改;this 是整 store
},
},
});

组件里用(Composition API,模板里最省事——store 本身是响应式的,<script setup> 返回后模板直接可读可写):

<script setup>
import { useCounterStore } from '../stores/counter';
const store = useCounterStore();
</script>

<template>
<p>{{ store.count }} ×2 = {{ store.double }}</p>
<button @click="store.inc()">+1</button>
<button @click="store.count++">直接加也生效</button> <!-- 没有 mutation,直接改就是合法操作 -->
</template>

4. 响应性的两条铁律(进篇 2/3前先记住)

实测行为(headless Node,setActivePinia 即可脱离组件使用 store):

  1. 直接解构会丢响应性const { count } = store 拿的是那一刻的快照。要解构且保持响应,用 storeToRefs(store)
  2. 改 state 就是要直接赋值$patch 只在你需要「一次改多个键 / 想留一条调试记录」时才用。
[实测] storeToRefs 解构改 n=7,store.n=7;而直接解构的 rawN 仍是 0(快照)。

5. 脱离组件用 store(测试 / 工具模块 / 路由守卫)

Pinia 不绑死在组件里——设一个「当前活动的 pinia」后,任何 .js/.mjs 都能 useStore()

import { createPinia, setActivePinia } from 'pinia';

setActivePinia(createPinia()); // 没装 app 也行;测试、SSR、纯逻辑层都靠它
const store = useCounterStore(); // 此刻真正实例化(懒创建)

所以本站本系列的「实测」都长这样:Node + setActivePinia 直接跑,不需要浏览器。

6. 版本与系列路线

  • 版本:pinia 4.0.3 / vue 3.5.42(2026-09)。v4 相对老教程的几个变化本系列会逐个点出:插件要等 app.use 才跑;setup store 的 $state 里放的是原始 ref(非解包值);$subscribe 默认在下一拍才回调(想同步用 flush: 'sync');setup store 的 setup 可收到一个 helpers 参数。
你会得到
三件套state / getters / actions 用法深解 + $patch / $reset,全部实测
组合式与多 storesetup store 语法、跨 store 调用、动态 store、无组件环境
解构·订阅·插件storeToRefs$subscribe/$onAction、MutationType、手写持久化插件
进阶与工程实战store 组织、TS 安全、SSR/测试/HMR、token·购物车场景
原理看 Pinia 源码:store 是怎么被“造”出来的、响应性与订阅的内幕

动手

  1. npm i pinia,把 §2 的挂载加到你的 Vue3 项目,写第一个 counter;
  2. 在组件里同时试 store.inc()store.count++,验证「直接改合法」;
  3. 用 §5 的 headless 姿势在 Node 里跑一个 store,体会「不装 app 也能用」。

自测

  1. Pinia 相比 Vuex 删掉了哪一层?改状态的两种合法姿势是什么?
  2. state 为什么必须是函数而不是对象?
  3. 组件里直接解构 store 会怎样?怎么保住响应性?
  4. 脱离组件用 store 需要先调什么?
  5. app.use(pinia) 除了注入实例还做了什么?(答案预告:触发插件)