一、Pinia 安装与注册
Pinia 是 Vue3 官方推荐的状态管理库,取代 Vuex:API 更简洁、天然支持 TS、无 mutations。
bash
npm install pinia
ts
// main.ts
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
const app = createApp(App);
app.use(createPinia());
app.mount("#app");
与 Vuex 对比
| 对比项 | Vuex | Pinia |
|---|---|---|
| mutations | 必须有(同步改 state) | 没有,直接改 state / actions |
| TypeScript | 类型推导弱 | 类型友好 |
| 模块 | modules 嵌套 | 每个 store 独立定义 |
| 体积 | 较大 | 更轻(仅 ~1KB core) |
二、defineStore 定义仓库
两种风格:选项式 (对象)与 setup 风格(函数),推荐 setup 风格。
2.1 选项式风格
ts
// stores/user.ts
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
state: () => ({
name: "",
token: localStorage.getItem("token") || "",
}),
getters: {
isLogin: (state) => !!state.token, // 派生状态
},
actions: {
setToken(token: string) {
this.token = token;
},
},
});
2.2 setup 风格(推荐)
ts
// stores/counter.ts
import { ref, computed } from "vue";
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", () => {
// state
const count = ref(0);
const double = ref(2);
// getters
const doubleCount = computed(() => count.value * 2);
// actions
function increment() {
count.value++;
}
return { count, double, doubleCount, increment };
});
| 构成 | 选项式写法 | setup 风格写法 |
|---|---|---|
| state | state: () => ({...}) |
ref() / reactive() |
| getters | getters: { xx(state) {...} } |
computed() |
| actions | actions: { xx() {...} } |
普通函数 |
三、组件中读取与修改 store
3.1 基础读写
vue
<script setup lang="ts">
import { useCounterStore } from "@/stores/counter";
const store = useCounterStore();
store.count++; // 直接修改 state(无需 mutations)
store.increment(); // 调用 action
</script>
<template>
<p>{{ store.count }} × 2 = {{ store.doubleCount }}</p>
<button @click="store.increment()">+1</button>
</template>
3.2 $patch 批量修改
ts
store.$patch({
count: store.count + 1,
double: store.count * 2,
});
// 函数式写法:可写逻辑
store.$patch((state) => {
state.count++;
state.double = state.count * 2;
});
3.3 storeToRefs 保持响应式
ts
import { storeToRefs } from "pinia";
import { useCounterStore } from "@/stores/counter";
const store = useCounterStore();
const { count, doubleCount } = storeToRefs(store); // 解构后仍是响应式
const { increment } = store; // action 直接解构
四、store 之间互相调用
ts
// stores/order.ts
import { defineStore } from "pinia";
import { useUserStore } from "./user";
export const useOrderStore = defineStore("order", () => {
const userStore = useUserStore(); // 在另一个 store 内直接使用
const orders = ref<number[]>([]);
function fetchOrders() {
// 读取其他 store 的状态
if (!userStore.isLogin) return;
orders.value = [1, 2, 3];
}
return { orders, fetchOrders };
});
五、异步请求写在 actions
ts
// stores/article.ts
import { ref } from "vue";
import { defineStore } from "pinia";
import api from "@/api"; // 封装好的请求
export const useArticleStore = defineStore("article", () => {
const list = ref<Article[]>([]);
const loading = ref(false);
async function fetchList(params: Query) {
loading.value = true;
try {
list.value = await api.getArticleList(params); // actions 支持 async/await
} finally {
loading.value = false;
}
}
return { list, loading, fetchList };
});
vue
<!-- 组件中 -->
<script setup lang="ts">
import { onMounted } from "vue";
import { useArticleStore } from "@/stores/article";
import { storeToRefs } from "pinia";
const store = useArticleStore();
const { list, loading } = storeToRefs(store);
onMounted(() => store.fetchList({ page: 1 }));
</script>
六、踩坑:解构丢失响应式
| 坑 | 现象 | 解决 |
|---|---|---|
| 直接解构 state | const { count } = store 后页面不更新 |
用 storeToRefs(store) |
| 解构 getters | 同上 | storeToRefs |
| 直接解构 actions | 丢失 this 上下文报错(选项式) |
action 直接 const { fn } = store(setup 风格无此问题) |
| 非响应式坑 | store 中存了普通对象(非 ref)被替换 | 用 ref/reactive 包裹 |
| $reset 不生效 | setup 风格 store 无内置 $reset | 手写 reset 函数返回初始值 |
| 组件外使用 store | 在普通工具函数中调用报错(未装 pinia) | 确保在 app.use(pinia) 后调用 |
ts
// 正确解构姿势
const { count, doubleCount } = storeToRefs(store); // state/getters
const { increment } = store; // actions
七、总结
- 定位:Vue3 官方状态管理,无 mutations、TS 友好,取代 Vuex;
- 定义 :
defineStore选项式或 setup 风格,state/getters/actions 三件套; - 读写 :直接改 state、
$patch批量、actions 承载异步; - 跨 store :store 内直接
useXxxStore()互相调用; - 异步:actions 用 async/await 写请求,loading 状态一并管理;
- 踩坑 :解构必须
storeToRefs,actions 直接解构,setup 风格 $reset 需手写。
本系列前篇:《Vue3 零基础上手》《Vue3 组合式 API 深度解析》《Vue3 组件开发》《Vue Router4 完整指南》;后续:《Vue3 项目工程实战》。