文章目录
- 1. 介绍
- 1.1 Pinia介绍
- 1.2 pinia的属性说明
- 2. 安装
- 3. 初步使用
- 4. store具体使用
- 4.1 值修改
- 4.2.1 直接修改
- 4.2.2 通过$patch整体修改
- 4.2.3 通过$patch函数式
- 4.2.4 通过$state整体修改
- 4.2.5 通过actions修改
- 4.2 解构store
- 5 actions使用
- 6. getters使用
- 6.1 通过this获取
- 6.2 通过state获取
- 6.3 传参使用
- 7.API
- 7.1 重置state($reset)
- 7.2 $subscribe
- 7.3 $onActon
- 8. 持久化插件
- 8.1 插件安装
- 8.2 使用
- 8.3 局限性
- 8.3.1 引用类型可能会失效
- 8.3.2 基本数据类型之外的不会被序列化
- 8.4 全局配置
1. 介绍
1.1 Pinia介绍
官网地址:https://pinia.vuejs.org/zh/
主要优点:
全局状态管理工具,Pinia.js有以下的特点:
- 完整的ts支持
- 体积小,只有1kb左右
- 去除mutations,只有state,getter,action
- actions支持同步与异步
- 代码扁平化没有模块嵌套,只有store的概念,store之间可以自由使用,每个store都是独立的。
- 无需手动添加store,store一旦创建便会自动添加
- 支持 vue2和vue3
- 支持插件扩展功能
什么时候可以使用?
保存全局信息的时候,例如登录的个人信息。
复杂的表单时,比如多个步骤的表单。
1.2 pinia的属性说明
defineStore方法的第一个参数:相当于为容器起一个名字。注意:这里的名字必须唯一,不能重复。
defineStore方法的第二个参数:可以简单理解为一个配置对象,里边是对容器仓库的配置。当然这种说明是以对象的形式存在。
state属性: 用来存储全局的状态的属性。
getters属性:用来监视或者说是计算状态的变化的,有缓存的功能。
actions属性:用来修改state全局状态数据的,复杂的计算逻辑可以放到这里。
2. 安装
pnpm i pinia
在main.ts中添加
import {createPinia} from 'pinia'
const store = createPinia()
app.use(store)
3. 初步使用
(1)在项目的src目录下新建store文件夹,
(2)在store文件夹里面新建store-name.ts和index.ts。
store-name用来定义枚举类型的名称作为store的唯一值。
store-name.ts
#这里定义了2个枚举名称分别为USER和SYSTEM
export const enum Names{
USER = "USER",
SYSTEM = "SYSTEM"
}
在index.ts中,
useUserStore是通过选项式类型Store
useSystemStore是组合式类似的Store
引入仓库插件:
import {defineStore} from 'pinia'
定义仓库1,指定的唯一id,是在使用时需要
这里的useXXXXXStore 是约定俗成的命名规范,下面就是选项式定义仓库的方式。
export const useXXXXXStore = defineStore("唯一id",{
state:()=>{},
getters:{},
actions:{}
}
以下两种分别介绍了选项式和组合式仓库定义的具体应用,针对唯一id,这里抽取出来作为独立的枚举类型。
也可以不用抽取,直接写,但是必须所定义的字符串是唯一的:
import {defineStore} from 'pinia'
import { Names } from './store-name'
import {ref} from "vue"
//options 选项式API
export const useUserStore = defineStore(Names.USER,{
//data: 类似于组件中的data
state:()=>{
return {
name: "张三",
age:20
}
},
//computed 修饰一些值
getters:{
},
//methods 可以做同步、异步,提交state
actions:{
}
})
//setup store 组合式API
export const useSystemStore = defineStore(Names.SYSTEM,()=>{
const systemInfo = ref({version:"win10"})
function getMemory(){
return "16G"
}
return {systemInfo, getMemory}
})
在vue中,分别调用2种不同store库,都可以使用:
<script setup lang='ts'>
import { useUserStore, useSystemStore } from "./store"
const user = useUserStore()
const system = useSystemStore()
</script>
<template>
<div>
<p>User Name:{{ user.name }}</p>
<p>User Age: {{ user.age }}</p>
</div>
<div>
<p>System Info: {{ system.systemInfo.version }}</p>
<p>System Memory: {{ system.getMemory() }}</p>
</div>
</template>
<style scoped></style>
页面显示如下:
4. store具体使用
4.1 值修改
4.2.1 直接修改
通过点击changeName和changeAge事件,可以直接修改用户的信息:
在点击按钮时,对应的pinia中保存的对象的值也发生改变。
4.2.2 通过$patch整体修改
//整体修改
const changeNameAndAge = () =>{
user.$patch({
name: names[randomInt(0, 4)],
age: user.age + 1
})
console.log(user.name,user.age)
}
4.2.3 通过$patch函数式
user.$patch((state) => {
state.age = randomInt(1,100)
state.name = checkName(state.age)
})
4.2.4 通过$state整体修改
这种方式通常需要将state中的对象全部写上,否则会报错,所以一般不推荐使用。
user.$state = {
name: "法外狂徒张三",
age: 30
}
4.2.5 通过actions修改
修改src/store/index.ts中useUserStore中actions:
然后在vue端调用:
const changeNameAndAge4 = ()=>{
user.setNameAndAge("张三",30)
}
4.2 解构store
直接解构Store中的对象时,是不具有响应式的,参考下图:
因此,为了具有响应式,需要添加pinia指定的组件storeToRefs。
第一步,先导入组件
import {storeToRefs} from "pinia"
第二步:在要解构的时候,将store对象包裹起来
const {name,age} = storeToRefs(user)
5 actions使用
模拟异步登录获取student信息
第一步,在store中定义异步方法getStudent
//定义数据类型
type Student = {
name: string,
grade: number
}
//模拟登陆时,2秒后获取到student的值
const loginUser = (): Promise<Student> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "小明",
grade: 3
})
}, 2000)
})
}
// 在actions中使用异步获取loginUser的数据
export const useStudentStore = defineStore("student", {
state: () => {
return {
student: <Student>{}
}
},
getters: {},
actions: {
async getStudent() {
this.student = await loginUser()
}
}
})
异步查询时,通常都是async和await搭配一起使用的。
第二步,在vue中使用:
//student
const studentStore = useStudentStore()
const getStudentInfo = () => {
studentStore.getStudent()
}
studentStore可以直接调用actions里面的方法getStudent()
除此之外,在actions中,方法也可以互相调用。
// actions中方法互调
// 当name参数有值时,就更新,否则使用登陆查询的值
export const useStudentStore = defineStore("student", {
state: () => {
return {
student: <Student>{}
}
},
getters: {},
actions: {
async getStudent(name:string|null) {
this.student = await loginUser()
this.updateStudent(name)
},
updateStudent(name:string|null){
if(name){
this.student.name = name
}
}
}
})
6. getters使用
6.1 通过this获取
getFullName():string {
return `${this.student.name}大学${this.student.grade}年级`
},
6.2 通过state获取
get的每个方法中都带有一个默认的state的参数,可以直接使用state来获取值
getFullName2(state){
return state.student.name + "大学" + state.student.grade + "年级"
},
6.3 传参使用
由于默认的参数是state,所以要想传参,需要返回一个带参数的方法提供调用就可以。
这里需要写成=>形式。
getFullName3: (state)=>{
return (firstName:string)=> firstName +"-" + state.student.name
}
在vue中使用时,可以传入对应的firstName变量值。
<p>student fullName3: {{ studentStore.getFullName3("张姓:") }}</p>
7.API
7.1 重置state($reset)
重置state为初始化的状态:
const resetStudentInfo = () => studentStore.$reset()
7.2 $subscribe
当state里面的值被修改时,就会触发该事件,
所以当需要监听新旧的值时,可以在此添加一些对应的逻辑判断。
studentStore.$subscribe((args,state)=>{
console.log("args===>",args)
console.log("state===>",state)
})
7.3 $onActon
本身onAction是在调用到actions中的事件时触发。
studentStore.$onAction((args)=>{
args.after(()=>{
console.log("after")
})
console.log("onAction args===>",args)
})
但是也可以做更精细的控制,就是在actions中的某个事件执行完之后再触发。
比如上面的1,2先分别调用了actions中的setStudent,updateStudent事件。
然后,再分别调用了args.after这个方法。
这种可以用来处理在某个actions动作发生后,添加一些处理逻辑。
8. 持久化插件
8.1 插件安装
state中的值,在页面进行刷新后,会丢失修改的数据,所以需要借助了浏览器的存储来持久化。
安装插件
pnpm i pinia-plugin-persistedstate
在main.ts中引入插件
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import {createPinia} from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' //引入
let app = createApp(App)
const pinia = createPinia()
app.use(pinia)
pinia.use(piniaPluginPersistedstate) //使用
app.mount('#app')
8.2 使用
第一种,简单使用,直接指定开启
export const useUserStore = defineStore(Names.USER, {
state: () => {},
getters: {},
actions: {},
persist: true //持久化开启
})
第二种: 指定自定义key
export const useUserStore = defineStore(Names.USER, {
state: () => {},
getters: {},
actions: {},
persist: {
key: "my-custom-key" //自定义key
}
})
第三种:组合式API方式的持久化,以及制定的存储类型
export const useSystemStore = defineStore(Names.SYSTEM, () => {
const systemInfo = ref({ version: "win10" })
function getMemory() {
return "16G"
}
return { systemInfo, getMemory }
},
{ //持久化,并自定义key
persist: {
key: "my-custom-key",
storage: sessionStorage, //sessionStorage,localStorage,
}
}
)
8.3 局限性
8.3.1 引用类型可能会失效
下面这种情况,b是对a的引用。因此,b和a都是指向同一个地址。
const a = {
1: 'one',
2: 'two',
// ...
}
const b = a
序列化之前,a===b 结果是true
在持久化之后,由于数据将会被序列化,因此引用在刷新时将会丢失。
再次反序列化之后,a和b有着相同的内容,但是指向的是不同的对象。
a === b 结果为false
解决方法:
采取避免 a 或 b 被持久化的方法(使用 paths 选项),
并使用 afterRestore 钩子在恢复数据后重新存储它们。
这样 a 和 b 就又会有着相同的引用,两者之间的联系就恢复了。
8.3.2 基本数据类型之外的不会被序列化
解决方法:
使用 afterRestore 钩子在恢复数据后重新创建对象。
使用自定义的 serializer 来配置你想要持久化的数据类型。
8.4 全局配置
使用createPersistedState来进行全局配置。
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import {createPinia} from 'pinia'
// import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { createPersistedState } from 'pinia-plugin-persistedstate'
let app = createApp(App)
const pinia = createPinia()
app.use(pinia)
// pinia.use(piniaPluginPersistedstate)
pinia.use(
createPersistedState({
storage: sessionStorage,
key: id => `__persisted__${id}`,
auto: true,
})
)
全局配置以后,可以不需要在每个store中再额外配置,但是可以指定的store中禁用持久化。
import { defineStore } from 'pinia'
defineStore('not-persisted', {
state: () => ({ saved: '' }),
persist: false, //显示指定某个store不持久化
})
也可以为单独的state中变量分别指定持久化方式:
import { defineStore } from 'pinia'
defineStore('store', {
state: () => ({
toLocal: '',
toSession: '',
toNowhere: '',
}),
persist: [
{
paths: ['toLocal'],
storage: localStorage,
},
{
paths: ['toSession'],
storage: sessionStorage,
},
],
})