Chapter 01

认识 Pinia

理解 Vue 状态管理的来龙去脉,搞清楚 Pinia 为何取代 Vuex 成为官方推荐,并完成安装与挂载。

1.1 为什么需要状态管理

在一个 Vue 应用里,组件之间靠 props 向下传、emit 向上抛来通信。但当"用户登录信息""购物车""主题设置"这类被很多不相关组件共享的状态出现时,逐层传递会变成噩梦(俗称 prop drilling)。

状态管理库把这类共享状态抽到组件树之外的一个集中仓库(store)里,任意组件都能直接读写,且变化是响应式的、可追踪的、可调试的。

State(状态)
store 里存储的响应式数据本身,例如 countusercartItems。相当于组件的 data,但全局共享。
Getters(取值器)
基于 state 派生出来的计算值,带缓存,相当于 store 的 computed,例如 cartTotal
Actions(动作)
修改 state 的方法,可同步可异步(发请求、改多个字段),相当于 store 的 methods
Store(仓库)
state + getters + actions 的容器。Pinia 中一个应用可以有多个独立 store。
Mutations(变更)
Vuex 特有概念:唯一能同步修改 state 的函数,必须通过 commit 调用。Pinia 已彻底移除它
Reactive(响应式)
数据变化时,依赖它的组件自动重新渲染。Pinia 基于 Vue 3 的 reactive/ref 实现。

1.2 从 Vuex 到 Pinia 的演进

Vue 的官方状态管理经历了三代:

官方定位

Pinia 的作者 Eduardo San Martin Morote 同时是 Vue Router 的维护者。Pinia 现在就是 “Vuex 5” 的事实实现——Vue 官方文档已把它列为默认状态管理推荐。

1.3 Pinia vs Vuex 对比

维度PiniaVuex 4
核心概念state / getters / actions(3 个)state / getters / mutations / actions(4 个)
修改 state直接改 或 action 中改必须 commit mutation
模块化天然扁平,多个 store 独立嵌套 modules + namespaced
TypeScript自动推断,无需辅助类型需大量泛型、体验差
体积(gzip)~1.5KB~3KB+
写法风格选项式 + 组合式仅选项式
DevTools深度集成、时间旅行支持但较弱
SSR一等支持需手动处理
一句话总结

Pinia = Vuex 去掉 mutations、去掉嵌套模块、加上完美 TS 推断和组合式写法。更少的概念,更少的模板代码。

1.4 安装

# 使用 npm / pnpm / yarn 任选其一
$ npm i pinia
$ pnpm add pinia
$ yarn add pinia

如果你用 Vue CLI 老项目,还需要 @vue/composition-api(Vue 2.7 以下);Vue 3 或 Vue 2.7+ 直接安装即可。

1.5 创建并挂载 Pinia 实例

Pinia 本身是一个"根实例",通过 createPinia() 创建,再用 app.use() 挂到 Vue 应用上。

// src/main.ts
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);   // 注册 Pinia,之后任意 store 才可用
app.mount("#app");

1.6 第一个 Store

defineStore 定义一个计数器 store,第一个参数是唯一 id(DevTools 中显示、SSR 序列化用)。

// src/stores/counter.ts
import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", {
  state: () => ({ count: 0 }),
  getters: {
    doubled: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
  },
});

1.7 在组件中使用

<!-- Counter.vue -->
<script setup lang="ts">
import { useCounterStore } from "@/stores/counter";

const counter = useCounterStore();
</script>

<template>
  <p>count: {{ counter.count }}</p>
  <p>doubled: {{ counter.doubled }}</p>
  <button @click="counter.increment()">+1</button>
</template>
命名约定

store 的组合函数一律以 use 开头(如 useCounterStore),这是 Pinia 官方约定,也符合 Vue 组合式函数的习惯。

1.8 Pinia 的工作原理速览

为什么必须先 app.use(pinia) 再用 store?因为 createPinia() 创建的实例会被"注入"到当前 Vue 应用中。当你调用 useCounterStore() 时,它会从当前活跃的 Pinia 实例里取(或懒创建)对应 store:

1.9 小结与下一步