当前位置: 首页 > news >正文

Vue状态管理工具:Pinia

基本概念

Pinia 是一个专为 Vue.js 设计的状态管理库,特别是针对 Vue 3 进行了优化,完美支持Vue3的Composition api 以及TypesCcript语法。它提供了一种更加简单、直观且可扩展的方式来组织和访问应用程序的状态。目前Pinia已经取代Vuex成为vue官方文档生态的一部分。

与vuex相比,Pinia主要有以下优点:

  1. Pinia 的 API 采用Vue 3的Composition API风格,设计更加简洁明了,易于理解和使用,状态管理更加直观。
  2. Pinia构建得更加轻量级,其体积比Vuex小,这有助于减少应用程序的加载时间和提高性能。
  3. Pinia 支持 TypeScript,提供了类型安全的 API ,有助于在开发过程中捕获错误和进行静态类型检查。
  4. Pinia支持创建多个store全局实例,每个store都可以视为一个独立的状态管理模块。
  5. Pinia 不需要嵌套模块,符合Vue3的Composition api ,让代码更加扁平化。

官方文档:Pinia | The intuitive store for Vue.js (vuejs.org)


使用步骤

1.安装Pinia

npm install pinia

2.main.js中引入pinia

import { createApp } from "vue";
import { createPinia } from "pinia"; //引入pinia
import App from "./App.vue";const pinia = createPinia(); //创建pinia实例
const app = createApp(App);
app.use(pinia);  //挂载实例
app.mount("#app");

3.创建仓库Option Store:store.js文件(后面会介绍Setup Store)

import { defineStore } from "pinia";export const useMainStore = defineStore("main", {state: () => ({}),getters: {},actions: {},
});
  1. defineStore的第一个参数:是容器的唯一标识,不能重复
  2. defineStore的第二个参数:是对容器仓库的配置说明(对象)
  3. state 属性: 存储全局状态数据data
  4. getters属性:计算属性,有缓存机制
  5. actions属性: 对state里数据变化的业务逻辑,简单说就是修改state全局状态数据

4.在组件中使用:需要引入和实例化

虽然我们前面定义了一个 store,但在我们使用 <script setup> 调用 useStore()(或者使用 setup() 函数,像所有的组件那样) 之前,store 实例是不会被创建的:

<script setup>
import { useMainStore } from "../stores/index";//引入
const store= useMainStore();//获取实例
// 可以在组件中的任意位置访问 `store` 变量 ✨
console.log(store);
</script>

核心概念

1. state

1).定义state

在大多数情况下,state 都是 store 的核心。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。

//index.js
export const useMainStore = defineStore("main", {state: () => ({name: "Jack",age: 18,})
});

2).访问state数据

//组件script中
import { useMainStore } from "../stores/index";//引入const store = useMainStore();
//方式一:
console.log(store.name,store.age);//输出 Jack 18//方式二:
const { name, age} = store //解构构赋值
console.log(name,age);//输出Jack 18//template模板中
{{store.name + store.age}}或解构的{{name + age}}

注意:虽然在数据较多的时候,方式二解构赋值相较于store.[‘变量名’]的方式更加简洁明了。但使用这种解构赋值法会丢失state数据的响应性。因为store 是一个用reactive 包裹的对象

解决方式是:使用官方提供的storeToRefs()方法。它将为每一个响应式属性创建引用。

import { storeToRefs } from "pinia"//引入
const { name, age} = storeToRefs(store)//使用

补充:其实在Vuex中,直接解构数据也是不可以的。


3).修改state数据

方式一:直接修改,通过对store.[属性],对访问的属性重新赋值

function changeState(){store.name = '小明'store.age = '666'
}

方式二:$patch方法修改,它允许你用一个 state 的补丁对象在同一时间更改多个属性(修改多条数据时,性能更高)

function changeState(){store.$patch({name: "王小明",age: 666,});
}

不过,用这种语法的话,有些变更真的很难实现或者很耗时:任何集合的修改(例如,向数组中添加、移除一个元素或是做 splice 操作)都需要创建一个新的集合。因此,$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。

//index.js
export const useMainStore = defineStore("main", {state: () => ({items: [],hasChanged: false,}),
});//组件内 修改
function changeState(){store.$patch((state) => {state.items.push({ name: 'shoes', quantity: 1 })state.hasChanged = true})
}

4).重置state

整个state的属性值都会变成定义时的初始值

function resetState(){store.$reset()
}

2. getter

1).定义getter

Getter 完全等同于 store 的 state 的计算值。推荐使用箭头函数,并且它将接收 state 作为第一个参数:

export const useMainStore = defineStore("main", {state: () => ({name: "Jack",age: 18,}),getters: {doubleAge: (state) => state.age * 2,//在getter中 使用另一个getter  this指向当前存储库addOneAge() {return this.doubleAge + 1;},//或使用箭头函数但要将getters作为参数传入。(this指向问题)addTowAge: (getters) => getters.doubleAge + 2,}
});

 2).使用getter

//组件script中
import { useMainStore } from "../stores/index";
const store = useMainStore();//方式一:
console.log(store.doubleAge);//方式二:解构赋值
import { storeToRefs } from "pinia"
const { doubleAge, addOneAge , addTowAge} = storeToRefs(store)
//.value访问计算属性的值
console.log(doubleAge.value,addOneAge.value, addTowAge.value);//temlate模板中
<p>{{store.doubleAge}}</p>
<p>{{store.addOneAge}}</p>
<p>{{store.addTowAge}}</p>

getter相当于state的计算属性,具有缓存机制。它的值会基于state依赖被缓存。仅会在state依赖被更新时才重新计算。只要依赖的state数据不变,无论访问多少次计算结果,它都会立即返回先前的计算结果,而不用重复执行 getter 函数。


 3. action

1).定义action

Action 相当于组件中的 method。它们是定义业务逻辑的完美选择。类似 getter,action 也可通过 this 访问整个 store 实例。不同的是,action 可以是异步的,你可以在它里面 await 调用任何 API,以及其他 action!

export const useCounterStore = defineStore("main", {state: () => ({count: 0,}),actions: {increment() {this.count++;},//模拟异步请求函数randomizeCounter() {//1 s后但会一个值未随机数字的promise对象return new Promise((resolve, reject) => {setTimeout(() => {let number = Math.round(100 * Math.random());resolve(number);}, 1000);});},//异步操作async changeCount() {const res = await this.randomizeCounter();//解析promisethis.count = res;//调用其他actionthis.increment();},},
});

 2).使用action

//script中
const store = useCounterStore();//方式一:
// 将 action 作为 store 的方法进行调用
store.changeCount()//方式二:
//解构赋值,直接使用
const { changeCount , increment }= store
changeCount()
increment()//template模板中
<button @click="store.changeCount()">changeCount</button>

 注意:作为 action 的 方法可以直接解构


其他补充

1. Setup Store

这是另一种定义 store 的语法。与 Vue 组合式 API 的 setup 函数相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。

import { defineStore } from "pinia";
import { ref } from "vue";export const useCounterStore = defineStore("counter", () => {const count = ref(0);const doubleCount = computed(() => count.value * 2);function increment() {count.value++;}return { count, doubleCount, increment };
});

在 Setup Store 中:ref() 就是 state 属性,computed() 就是 getters,function() 就是 actions

注意,要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性。这意味着,你不能在 store 中使用私有属性。不完整返回会影响 SSR ,开发工具和其他插件的正常运行。

相比于上面介绍的Option Store,Setup store拥有更高的灵活性,因为你可以在store内创建侦听器,并自由地使用任何组合式函数。

Setup store 也可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用 inject() 访问,就像在组件中一样:

import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'export const useSearchFilters = defineStore('search-filters', () => {const route = useRoute()// 这里假定 `app.provide('appProvided', 'value')` 已经调用过const appProvided = inject('appProvided')// ...return {// ...}
})

总之,两种语法都有各自的优势和劣势,Option Store更容易使用,而Setup Store更灵活和强大。


2. pinia持久化存储

pinia 和 vuex 一样,数据是短时的,只要一刷新页面,数据就会恢复成初始状态,为了避免这个问题,可以对其采用持久化保存方法。

持久化保存的原理是在 pinia 中数据更新时,同步保存到 localStorage 或 sessionStorage 中,刷新后从本地存储中读取数据。

1).安装插件

官方文档:开始使用 |Pinia 插件持续存在

npm i pinia-plugin-persistedstate

2).引入使用插件:main.js文件

import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia"; //引入pinia
import persist from 'pinia-plugin-persistedstate'//引入插件const pinia = createPinia(); //创建pinia实例
pinia.use(persist)//使用插件const app = createApp(App);
app.use(pinia); //挂载pinia到vue实例
app.mount("#app");

3).使用插件

方式一:默认保存,将当前模块中的所有数据都进行持久化存储,保存在localStorage 或sessionStorage 中,刷新页面不需要手动读取数据,插件会自动读取。

//setup store
defineStore("counter",() => {return { };},{ persist: true }// true 表示开启持久化保存
);//option store
defineStore("main", {state: () => ({}),actions: {},persist: true,
});

方式二:传递一个对象给 Store 的 persist 属性来配置持久化。

persist: {key: "counterStore", //存储名称storage: sessionStorage, // 存储方式paths: ["count"], //paths指定 state 中哪些数据需要被持久化。//[] 表示不持久化任何状态,undefined 或 null 表示持久化整个 state},

效果如图:

64ffbf5388444e4b81e2e0553075f4d1.png


3. 封装pinia

 封装的目的是pinia独立维护,减少main.js文件的冗余度。同时因为Pinia支持创建多个store全局实例,封装pinia可更加方便统一管理这些仓库。举例说明:

1).创建仓库userStore:user.js文件

import { defineStore } from "pinia";export const useUserStore = defineStore("user", {state: () => ({name: "Jack",age: 18,}),getters: {doubleAge: (state) => state.age * 2,},actions: {increment() {this.age++;},},persist: true,
});

2).创建仓库counterStore:counter.js文件

import { defineStore } from "pinia";
import { ref, computed } from "vue";export const useCounterStore = defineStore("counter",() => {//数据stateconst count = ref(1);//getterconst doubleCount = computed(() => count.value * 2);//actionfunction increment() {this.count++;}return {count,doubleCount,increment,};},{persist: true,}
);

3).封装pinia

import { createPinia } from "pinia"; //引入pinia
import persist from "pinia-plugin-persistedstate"; //引入持久化插件const pinia = createPinia(); //创建pinia实例
pinia.use(persist); //使用插件export default pinia; //导出pinia用于main.js注册//统一导出,可以控制各个仓库是否可用
// import { useUserStore } from '@/stores/modules/user.js'
// export { useUserStore }
// import { useCounterStore } from '@/stores/modules/counter.js'
// export { useCounterStore }// 简写
export * from "./modules/user";
export * from "./modules/counter";

4).在mian.js中注册pinia

import { createApp } from "vue";
import App from "./App.vue";
import pinia from "./stores/index";const app = createApp(App);app.use(pinia); //挂载pinia到vue实例
app.mount("#app");

文件结构: 

03cf1ac7a31f4a7785152d90a7a42bd3.png

至此pinia封装完成,由于pinia具有较强的可扩展性。如果需要添加其他插件扩展便可一并放入index.js文件独立维护。参考官方文档:插件 | Pinia (vuejs.org)

 5).在组件中使用:只需要从index.js文件中导入,减少代码复杂度

import { useCounterStore } from '../stores/index'
const counterStore = useCounterStore()

 若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 接口自动化-代码实现
  • SpringBoot 设置传入参数非必要
  • leetcode每日一题49
  • 微信小程序的四种弹窗使用
  • 【计算机操作系统】段页式管理方式
  • 【网络安全】IDOR之邮箱银行报价
  • 全面讲解Vue中的toRaw函数
  • Go第一个程序
  • 高性能web服务器2——Nginx概述
  • STM32 —— TIM(基本定时器)详解_stm32的tim
  • 实验十 编写子程序《汇编语言》- 王爽
  • 设计者模式:深度解析及应用
  • DC-DC 转换器中的压电谐振器:当前状态和限制
  • Ps:首选项 - 性能
  • RabbitMQ集群 - 普通集群搭建、宕机情况
  • ➹使用webpack配置多页面应用(MPA)
  • 0基础学习移动端适配
  • 4个实用的微服务测试策略
  • E-HPC支持多队列管理和自动伸缩
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • Java小白进阶笔记(3)-初级面向对象
  • JSDuck 与 AngularJS 融合技巧
  • js递归,无限分级树形折叠菜单
  • js作用域和this的理解
  • Logstash 参考指南(目录)
  • maya建模与骨骼动画快速实现人工鱼
  • PHP 小技巧
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • 关于 Cirru Editor 存储格式
  • 技术攻略】php设计模式(一):简介及创建型模式
  • 简单基于spring的redis配置(单机和集群模式)
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 携程小程序初体验
  • 赢得Docker挑战最佳实践
  • 优秀架构师必须掌握的架构思维
  • 自动记录MySQL慢查询快照脚本
  • AI算硅基生命吗,为什么?
  • 阿里云移动端播放器高级功能介绍
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • # Apache SeaTunnel 究竟是什么?
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • # 飞书APP集成平台-数字化落地
  • # 数论-逆元
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (0)Nginx 功能特性
  • (39)STM32——FLASH闪存
  • (C++17) optional的使用
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (k8s中)docker netty OOM问题记录
  • (二)丶RabbitMQ的六大核心
  • (含笔试题)深度解析数据在内存中的存储
  • (回溯) LeetCode 78. 子集
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (每日一问)操作系统:常见的 Linux 指令详解
  • (全注解开发)学习Spring-MVC的第三天