vue3 - pinia状态管理库
概念
Pinia 是 Vue 官方推荐的状态管理库,是 Vuex 的继任者(Vuex 作者同一人开发),专门为 Vue 3 设计,完全支持 Composition API 和 TypeScript。它简化了状态管理的流程,提供了更简洁的 API 和更好的开发体验。
核心特点
- 简洁的 API
去掉了 Vuex 中的mutation(突变),直接通过actions或直接修改状态(对state直接赋值),减少模板代码。 - 完全支持 TypeScript
类型推断更友好,无需手动声明类型即可获得完整的类型提示,开发时能及时发现类型错误。 - 与 Composition API 无缝集成
可以在setup中轻松使用,支持直接解构状态而不失去响应性。 - 模块化设计
每个store都是一个独立的模块,无需像 Vuex 那样嵌套modules,结构更清晰。 - 轻量体积
核心代码仅约 1KB,性能优于 Vuex。
Pinia 核心概念
- Store(仓库):存储应用状态的容器,每个 Store 是一个独立的模块
- State:存储的核心数据
- Getters:基于 State 的计算属性
- Actions:用于修改 State 的方法(支持异步操作)
安装pinia
npm i pinia
使用 pinia
src\main.ts
import { createApp } from "vue";
import App from "./App.vue";
// 第一步:引入pinia
import { createPinia } from "pinia";
const app = createApp(App);
// 第二步:创建pinia
const pinia = createPinia();
// 第三步:安装pinia
app.use(pinia);
app.mount("#app");
安装成功

存储和读取数据
创建store
src\store\sum.ts
import { defineStore } from "pinia";
// 定义并导出一个 store
export const useSumStore = defineStore("sum", {
// 状态
state: () => ({
sum: 4,
}),
});
在组件中的使用
src\components\SumComponent.vue
<template>
<div class="sum-container">
<h3>当前求和为:{{ sum }}</h3>
<select v-model="number">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
<button @click="add">加</button>
<button @click="subtract">减</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useSumStore } from '@/store/sum';
// const sum = ref(4);
const sum = useSumStore().sum;
const number = ref('1');
const add = () => {
// sum.value += parseInt(number.value);
};
const subtract = () => {
// sum.value -= parseInt(number.value);
};
</script>修改数据的三种方式
第1种:直接改值
useSumStore().sum += parseInt(number.value);
第2种:$patch
调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性:
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
第3种方式actions
src\store\sum.ts
import { defineStore } from "pinia";
// 定义并导出一个 store
export const useSumStore = defineStore("sum", {
// 状态
state: () => ({
sum: 4,
}),
actions: {
increment(value: number) {
this.sum += value;
},
},
});src\components\SumComponent.vue
<template>
<div class="sum-container">
<h3>当前求和为:{{ useSumStore().sum }}</h3>
<select v-model="number">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
<button @click="add">加</button>
<button @click="subtract">减</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useSumStore } from '@/store/sum';
// const sum = ref(4);
const number = ref('1');
const add = () => {
//第1种写法:直接改值
// useSumStore().sum += parseInt(number.value);
//第2种写法:$patch
// useSumStore().$patch({
// sum: useSumStore().sum + parseInt(number.value),
// });
//第3种写法:actions 方法
useSumStore().increment(parseInt(number.value));
};
const subtract = () => {
// sum.value -= parseInt(number.value);
useSumStore().sum -= parseInt(number.value);
};
</script>storeToRefs 解构
在 Pinia 中,storeToRefs 是一个非常实用的工具函数,用于将 Pinia 仓库(store)中的状态(state)和 getter 转换为响应式的 ref 对象,方便在组件中使用。
const sumStore = useSumStore();
const { sum } = storeToRefs(sumStore); // 获取store中的值
console.log("toRefs", toRefs(sumStore))
console.log("storeToRefs", storeToRefs(sumStore))

所以
<template>
<div class="sum-container">
<h3>当前求和为:{{ sum }}</h3>
<select v-model="number">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
<button @click="add">加</button>
<button @click="subtract">减</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useSumStore } from '@/store/sum';
import { storeToRefs } from 'pinia';
// const sum = ref(4);
const number = ref('1');
const sumStore = useSumStore();
const { sum } = storeToRefs(sumStore); // 获取store中的值
const add = () => {
sumStore.increment(parseInt(number.value));
};
const subtract = () => {
// sum.value -= parseInt(number.value);
sumStore.sum -= parseInt(number.value);
};
</script>getters
基本使用
Getter 完全等同于 store 的 state 的计算值。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:
export const useSumStore = defineStore("sum", {
// 状态
state: () => ({
sum: 4,
}),
actions: {
increment(value: number) {
this.sum += value;
},
},
getters: {
bigSumNumber: (state) => {
return state.sum * 10;
}
},
});
访问其他 getter
大多数时候,getter 仅依赖 state。不过,有时它们也可能会使用其他 getter。因此,即使在使用常规函数定义 getter 时,我们也可以通过 this 访问到整个 store 实例,但(在 TypeScript 中)必须定义返回类型。这是为了避免 TypeScript 的已知缺陷,不过这不影响用箭头函数定义的 getter,也不会影响不使用 this 的 getter。
import { defineStore } from "pinia";
// 定义并导出一个 store
export const useSumStore = defineStore("sum", {
// 状态
state: () => ({
sum: 4,
}),
actions: {
increment(value: number) {
this.sum += value;
},
},
getters: {
bigSumNumber: (state) => {
return state.sum * 10;
},
// 返回类型**必须**明确设置
newSumNumber(): number {
// 整个 store 的 自动补全和类型标注 ✨
return this.sum * 10;
},
},
});
向 getter 传递参数
Getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,你可以从 getter 返回一个函数,该函数可以接受任意参数:
js
export const useUserListStore = defineStore('userList', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
并在组件中使用:
<script setup>
import { useUserListStore } from './store'
const userList = useUserListStore()
const { getUserById } = storeToRefs(userList)
// 请注意,你需要使用 `getUserById.value` 来访问
// <script setup> 中的函数
</script>
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>
请注意,当你这样做时,getter 将不再被缓存。它们只是一个被你调用的函数。不过,你可以在 getter 本身中缓存一些结果,虽然这种做法并不常见,但有证明表明它的性能会更好:
export const useUserListStore = defineStore('userList', {
getters: {
getActiveUserById(state) {
const activeUsers = state.users.filter((user) => user.active)
return (userId) => activeUsers.find((user) => user.id === userId)
},
},
})
订阅 state:subscribe 方法
类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次 (例如,当使用上面的函数版本时)。
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// 和 cartStore.$id 一样
mutation.storeId // 'cart'
// 只有 mutation.type === 'patch object'的情况下才可用
mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('cart', JSON.stringify(state))
})
刷新时机
在底层实现上,$subscribe() 使用了 Vue 的 watch() 函数。你可以传入与 watch() 相同的选项。当你想要在 每次 state 变化后立即触发订阅时很有用:
cartStore.$subscribe((mutation, state) => {
// 每当状态发生变化时,将整个 state 持久化到本地存储
localStorage.setItem('cart', JSON.stringify(state))
}, { flush: 'sync' })
取消订阅
默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离:
<script setup>
const someStore = useSomeStore()
// 此订阅器即便在组件卸载之后仍会被保留
someStore.$subscribe(callback, { detached: true })
</script>
TIP
你可以在
pinia实例上使用watch()函数侦听整个 state。watch( pinia.state, (state) => { // 每当状态发生变化时,将整个 state 持久化到本地存储。 localStorage.setItem('piniaState', JSON.stringify(state)) }, { deep: true } )
store 的组合式写法
选项式写法:
import { defineStore } from "pinia";
import axios from "axios";
// 定义并导出一个 store
export const useTalksStore = defineStore("talks", {
// 状态
state: () => ({
talks: [
{
id: 1,
title: "你今天有点怪,哪里怪?怪好看的!",
},
{
id: 2,
title: "草莓、蓝莓、蔓越莓,你想我了没?",
},
{
id: 3,
title: "心里给你留了一块地,我的死心塌地",
},
],
}),
actions: {
async getTalk() {
let loading = true;
let errorMsg = "";
try {
const { data } = await axios.get(`/src/data/lovetasks.json`);
console.log(data);
const maxId =
this.talks.length > 0
? Math.max(...this.talks.map((talk) => talk.id)) + 1
: 0;
for (const key in data) {
let talk = data[key].title;
//判断tasks的title是否包含当前task的title
if (!this.talks.some((item) => item.title === talk)) {
this.talks.unshift({ id: maxId, title: talk });
return;
}
}
} catch (err) {
errorMsg = "网络错误,请稍后重试";
console.error(err);
} finally {
loading = false;
}
},
},
});
改为组合式写法,不要忘记 return:
import { defineStore } from "pinia";
import axios from "axios";
import { reactive } from "vue";
//组合式写法
export const useTalksStore = defineStore("talks", () => {
//state
const talks = reactive([
{
id: 1,
title: "你今天有点怪,哪里怪?怪好看的!",
},
{
id: 2,
title: "草莓、蓝莓、蔓越莓,你想我了没?",
},
{
id: 3,
title: "心里给你留了一块地,我的死心塌地",
},
]);
//actions
async function getTalk() {
let loading = true;
let errorMsg = "";
try {
const { data } = await axios.get(`/src/data/lovetasks.json`);
console.log(data);
const maxId =
talks.length > 0 ? Math.max(...talks.map((talk) => talk.id)) + 1 : 0;
for (const key in data) {
let talk = data[key].title;
//判断tasks的title是否包含当前task的title
if (!talks.some((item) => item.title === talk)) {
talks.unshift({ id: maxId, title: talk });
return;
}
}
} catch (err) {
errorMsg = "网络错误,请稍后重试";
console.error(err);
} finally {
loading = false;
}
}
return { talks, getTalk };
});

浙公网安备 33010602011771号