banner
NEWS LETTER

Pinia 极速入门:核心概念与入门指南

Scroll down

Pinia极速入门:核心概念与入门指南

前言:作为 Vue3 的官方推荐状态管理库,Pinia 比 Vuex 更轻量、更简单,且完美支持 TypeScript。本文将带你通过“项目实战”的方式,快速掌握 Pinia 的核心用法,并避开新手最容易踩的坑。

1. 为什么选择 Pinia?

简单来说,Pinia 去掉了 Vuex 中复杂的 Mutation,只保留了 StateGettersActions。它用起来就像是一个全局的组件,非常符合 Vue3 组合式 API 的思维。

2. 快速开始

首先,在 main.js 中注册 Pinia。这是所有故事的开始。

文件路径: src/main.js

1
2
3
4
5
6
7
8
9
10
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

选项式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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;

组合式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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) 使用 refreactive
Getters getters: { double() {...} } const double = computed(() => ...) 使用 computed
Actions actions: { inc() {...} } function inc() { ... } 使用普通函数
跨 Store 在 getter/action 内部调用 在函数体顶部直接调用 useX() 作用域更清晰
访问数据 this.count count.value 最容易出错的地方!

为什么推荐用组合式写法?

  1. 更强的逻辑复用:你可以在 Store 内部使用 Vue 的 watchonMounted 甚至提取出来的 Hooks (Composables),这在选项式写法中很难做到。
  2. 代码组织更自由:你不再受限于 stategettersactions 三个区块。你可以把相关的 state 和 function 写在一起,这对大型 Store 非常友好。
  3. 类型推导更好:对于 TypeScript 用户来说,Setup Store 的类型推导通常比 Option Store 更顺畅。

温馨提示:

换成组合式写法后,在组件中解构数据时有一个经典的“坑”(丢失响应性)。


4. 组件中使用与“解构陷阱”

这是新手最容易踩的坑:直接解构 Store 会导致响应式丢失!

场景:你想在组件里直接用 count,而不是 counterStore.count

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<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 性能更好,语义更清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
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 需要自己实现)

1
counterStore.$reset(); // count 回到 99, friends 回到初始数组

6. 总结

  1. 定义:使用 defineStore,结构类似 Vue 组件 (state, getters, actions)。
  2. 使用:组件内 const store = useStore()
  3. 避坑解构 State 必须用 storeToRefs,否则失去响应式。
  4. 修改:可以直接修改 store.count++,也可以用 Actions 或 $patch

希望这篇笔记能帮你快速上手 Pinia!如果有任何疑问,欢迎在评论区留言交流。

其他文章
目录导航 置顶
  1. 1. Pinia极速入门:核心概念与入门指南
  2. 2. 1. 为什么选择 Pinia?
  3. 3. 2. 快速开始
  4. 4. 3. 核心概念三剑客
    1. 4.1. 3.1 定义 Store (Defining a Store)
    2. 4.2. 核心映射关系表
    3. 4.3. 为什么推荐用组合式写法?
  5. 5. 4. 组件中使用与“解构陷阱”
  6. 6. 5. 常用简便方法
    1. 6.1. 5.1 $patch:同时修改多个状态
    2. 6.2. 5.2 $reset:重置状态
  7. 7. 6. 总结