Pinia极速入门:核心概念与入门指南
前言:作为 Vue3 的官方推荐状态管理库,Pinia 比 Vuex 更轻量、更简单,且完美支持 TypeScript。本文将带你通过"项目实战"的方式,快速掌握 Pinia 的核心用法,并避开新手最容易踩的坑。
1. 为什么选择 Pinia?
简单来说,Pinia 去掉了 Vuex 中复杂的 Mutation,只保留了 State 、Getters 、Actions。它用起来就像是一个全局的组件,非常符合 Vue3 组合式 API 的思维。
2. 快速开始
首先,在 main.js 中注册 Pinia。这是所有故事的开始。
文件路径 : src/main.js
javascript
import { createApp } from "vue";
import { createPinia } from "pinia"; // 导入 createPinia
import App from "./App.vue";
const app = createApp(App);
const pinia = createPinia(); // 创建实例
app.use(pinia); // 注册插件
app.mount("#app");
3. 核心概念三剑客
3.1 定义 Store (Defining a Store)
我们以 counter.js 为例。虽然 Pinia 支持"组合式写法 (Setup Store)",但为了方便上手,我们先看从 Vuex 过度更自然的"选项式写法 (Option Store)"。
文件路径 : src/stores/counter.js
选项式
javascript
import { defineStore } from "pinia";
import useUser from "./user"; // 引入其他 Store
export const useCounter = defineStore("counter", {
// 1. State: 相当于组件的 data,用于存储数据
state: () => ({
count: 99,
friends: [
{ id: 111, name: "why" },
{ id: 112, name: "kobe" },
],
}),
// 2. Getters: 相当于组件的 computed,用于计算数据
getters: {
doubleCount(state) {
return state.count * 2;
},
// 支持使用 this 访问其他 getter
doubleCountAddOne() {
return this.doubleCount + 1;
},
// 跨 Store 访问
showMessage(state) {
const userStore = useUser();
return `name:${userStore.name}-count:${state.count}`;
}
},
// 3. Actions: 相当于组件的 methods,用于修改数据(支持异步!)
actions: {
increment(num) {
// 也可以直接通过 this.count++ 修改,但封装 action 更规范
this.count += num;
},
},
});
export default useCounter;
组合式
javascript
import { defineStore } from "pinia";
import { ref, computed } from "vue"; // 引入 Vue 的核心响应式 API
import useUser from "./user"; // 引入其他 Store
// 第二个参数传入一个函数,而不是对象
export const useCounter = defineStore("counter", () => {
// --- 1. State (对应 ref/reactive) ---
const count = ref(99);
const friends = ref([
{ id: 111, name: "why" },
{ id: 112, name: "kobe" },
]);
// --- 引入其他 Store (在函数体内直接调用即可) ---
const userStore = useUser();
// --- 2. Getters (对应 computed) ---
const doubleCount = computed(() => {
return count.value * 2; // 注意:在 script 中访问 ref 需要 .value
});
// 访问内部其他 computed
const doubleCountAddOne = computed(() => {
return doubleCount.value + 1;
});
// 跨 Store 访问
const showMessage = computed(() => {
// 直接使用上面定义的 userStore 变量
return `name:${userStore.name}-count:${count.value}`;
});
// --- 3. Actions (对应普通 function) ---
function increment(num) {
count.value += num;
}
// --- 4. 必须 return ---
// 将需要暴露给组件使用的状态和方法返回
return {
count,
friends,
doubleCount,
doubleCountAddOne,
showMessage,
increment,
};
});
export default useCounter;
核心映射关系表
为了帮助你更好地理解这种转换,这里有一个简单的对照表:
| 概念 | 选项式 (Option Store) | 组合式 (Setup Store) | 关键点 |
|---|---|---|---|
| 定义方式 | defineStore('id', { ... }) |
defineStore('id', () => { ... }) |
参数变为函数 |
| State | state: () => ({ count: 0 }) |
const count = ref(0) |
使用 ref 或 reactive |
| Getters | getters: { double() {...} } |
const double = computed(() => ...) |
使用 computed |
| Actions | actions: { inc() {...} } |
function inc() { ... } |
使用普通函数 |
| 跨 Store | 在 getter/action 内部调用 | 在函数体顶部直接调用 useX() |
作用域更清晰 |
| 访问数据 | this.count |
count.value |
最容易出错的地方! |
为什么推荐用组合式写法?
- 更强的逻辑复用 :你可以在 Store 内部使用 Vue 的
watch、onMounted甚至提取出来的 Hooks (Composables),这在选项式写法中很难做到。 - 代码组织更自由 :你不再受限于
state、getters、actions三个区块。你可以把相关的 state 和 function 写在一起,这对大型 Store 非常友好。 - 类型推导更好:对于 TypeScript 用户来说,Setup Store 的类型推导通常比 Option Store 更顺畅。
温馨提示:
换成组合式写法后,在组件中解构数据时有一个经典的"坑"(丢失响应性)。
4. 组件中使用与"解构陷阱"
这是新手最容易踩的坑:直接解构 Store 会导致响应式丢失!
场景 :你想在组件里直接用 count,而不是 counterStore.count。
javascript
<script setup>
import useCounter from "@/stores/counter";
import { storeToRefs } from "pinia"; // 关键工具!
const counterStore = useCounter();
// 错误做法:直接解构,count 变成普通变量,不再响应式
// const { count } = counterStore;
// 正确做法:使用 storeToRefs 包裹
const { count } = storeToRefs(counterStore);
// 注意:Actions (方法) 可以直接解构,不需要 storeToRefs
const { increment } = counterStore;
</script>
<template>
<div>
<!-- 使用 store 实例访问 -->
<h2>Count (Store): {{ counterStore.count }}</h2>
<!-- 使用解构后的响应式数据 -->
<h2>Count (Ref): {{ count }}</h2>
<button @click="increment(1)">+1</button>
</div>
</template>
原理小贴士 :
storeToRefs会把 State 和 Getters 里的属性都变成 Ref 对象,而保留 Actions 这样解构出来依然保持响应式引用。
5. 常用简便方法
除了基础用法,Pinia 还提供了一些极其好用的 API,能极大提升开发效率。
5.1 $patch:同时修改多个状态
如果你需要一次性修改多个数据,用 $patch 性能更好,语义更清晰。
javascript
const handleClick = () => {
// 方式一:对象式
counterStore.$patch({
count: 100,
friends: [...counterStore.friends, { id: 114, name: 'curry' }]
});
// 方式二:函数式 (适合复杂逻辑)
counterStore.$patch((state) => {
state.count++;
state.friends.push({ id: 115, name: 'durant' });
});
};
5.2 $reset:重置状态
想把状态恢复到初始值?不再需要手动写 reset 函数了。
(注意:仅限 Option Store 写法支持,Setup Store 需要自己实现)
javascript
counterStore.$reset(); // count 回到 99, friends 回到初始数组
6. 总结
- 定义 :使用
defineStore,结构类似 Vue 组件 (state,getters,actions)。 - 使用 :组件内
const store = useStore()。 - 避坑 :解构 State 必须用
storeToRefs,否则失去响应式。 - 修改 :可以直接修改
store.count++,也可以用 Actions 或$patch。
希望这篇笔记能帮你快速上手 Pinia!如果有任何疑问,欢迎在评论区留言交流。