有时,我们希望几个组件共享某一个数据,如果通过 Vue3 的参数传递将会很混乱,集中式状态(数据)管理插件就是用于解决这一问题的。在 Vue2 中,推荐使用的是 vuex;而在 Vue3,官方推荐使用 Pinia 用作集中式状态管理插件。
环境准备
npm install pinia
安装 Pinia
只需简单三步,即可在您的项目中使用 Pinia.
main.ts:
import {createApp} from "vue";
import App from "@/App.vue";
// 1. 引入 pinia
import {createPinia} from "pinia";
const app = createApp(App);
// 2. 创建一个 Pinia 实例
const pinia = createPinia();
// 3. 安装 pinia
app.use(pinia);
app.mount("#app")
使用 Pinia
store
是一个保存状态、业务逻辑的实体,每个组件都可以读取、写入它。其包含三个概念:state
、getter
、action
,分别对应组件中的 data
、computed
和 methods
。
编码规范要求我们,在存储数据时,将脚本文件放在
src/store/
下,并使用useXxxStore
命名暴露的store
。
存储数据(选项式)
src/store/Counter.ts
:
import {defineStore} from "pinia";
export const useCounterStore = defineStore('counter', {
// 数据
state() {
return {
sum: 6
}
},
// 操作
actions: {
add(val: number) {
// this 是当前 store 实例
this.sum += val;
},
sub(val: number) {
this.sum -= val;
}
},
// 计算属性
getters: {
doubleSum(): number {
return this.sum * 2
},
/*使用箭头函数*/
halfSum: state => state.sum / 2
}
});
存储数据(组合式)
src/store/Counter.ts
:
import {defineStore} from "pinia";
import {computed, ref} from "vue";
export const useCounterStore = defineStore('counter', () => {
// 数据
const sum = ref(6);
// 操作
function add(val: number) {
sum.value += val;
}
function sub(val: number) {
sum.value -= val;
}
// 计算方法
const doubleSum = computed(() => sum.value * 2);
const halfSum = computed(() => sum.value / 2);
// 最终需要暴露出去
return {
sum,
add,
sub,
doubleSum,
halfSum
}
});
读取数据
src/components/Counter.vue
(或任何其他组件):
<script setup lang="ts">
import {ref} from "vue";
// 引入定义的store
import {useCounterStore} from "@/store/Conuter";
const counterStore = useCounterStore();
let n = ref(1);
</script>
<template>
<div class="counter">
<!-- counterStore.sum 本身是响应式的,可以读写 -->
<h2>当前求和为:{{ counterStore.sum }}</h2>
<!-- v-model 的作用是,当select的值发生变化时,会自动更新n的值,v-model.number 是把n转成number类型 -->
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<!-- 可以直接使用counterStore的add和sub方法 -->
<button @click="counterStore.add(n)">加</button>
<button @click="counterStore.sub(n)">减</button>
</div>
</template>
<style scoped>
.counter {
margin: 20px;
background-color: skyblue;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
select, button {
margin: 0 5px;
height: 25px;
}
</style>
但在使用数据时,总是使用 counterStore.
前缀太冗长,是否可以将数据本身直接解构出来?
当然可以!但是,使用 toRefs
保留解构数据的响应式是一种不被推荐的做法!因为 toRefs
会将所有属性都添加响应式,这会影响性能,好在 Pinia 提供了更快的解决方案:使用 storeToRefs(obj)
:
// 只会给store中的数据进行响应式代理
let {sum} = storeToRefs(counterStore);
storeToRefs
只会给存储的数据添加响应式。
订阅数据的变化
使用 $subscribe(callback)
可以实现监听 store
里面数据的变化,并执行响应的回调函数。
例如可以根据这个函数结合 localStorage
实现 sum
的存储:
// 监听数据变化,并保存到localStorage中
counterStore.$subscribe((mutation, state) => {
console.log('数据更新了');
localStorage.setItem('sum', state.sum.toString());
})
其中,mutation
记录了变化的相关信息,state
就是监听的 store
的数据。
在 src/storage/Counter.ts
中:
export const useCounterStore = defineStore('counter', {
// 数据
state() {
return {
// 从本地缓存中初始化
sum: parseInt(localStorage.getItem('sum') || '0')
}
},
或者组合式:
// 从本地缓存中初始化
const sum = ref(parseInt(localStorage.getItem('sum') || '0'));