Vue3 官推的状态管理 Pinia
一、Pinia是什么?
在Vue2中一般采用Vuex进行状态管理,在Vue3中推荐使用Pinia,这Vue3中推荐使用Pinia,这是最新一代轻量级状态管理插件。Vuex将不再接受新的功能。推荐使用Pinia。
二、Pinia的特点
- 支持Vue2和Vue3,两者都可以使用Pinia;
- 语法简洁,支持Vue3中的setup的写法,不必像Vue2中那样定义state、mutations、actions、getters等,可以按照setup Composition API的方式返回状态和改变状态的方法实现代码的扁平化;
- 支持Vuex中state、actions、getters形式的写法,丢弃了mutations开发时候不用根据同步异步来决定mutation或者actions,Pinia中只有actions;
- 对TyppeScript支持非常友好;
三、Pinia的使用
1.npm install pinia -s
2.创建pinia实例
// src/store/index.ts
import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia ;
3.注册到App实例上
// src/main.ts
import App from './App.vue';
import { createApp } from 'vue';
import pinia from '@/store';
const app = createApp(App);
app.use(pinia);
4.模块化管理
defineStore()方法的第一个参数:为仓库起一个名字呢,不可重复,唯一的;
defineStore()方法的第二个参数:有setup和option两种写法
// src/store/modules/demo.ts(setup写法)
import { defineStore } from 'pinia';
import { store } from '/@/store';
const useDemoStore = defineStore('demo',()=>{
const counter = ref(0);
const increment = ()=>{
counter.value++;
}
return {counter,increment}
});
export default useDemoStore;
// src/store/modules/demo.ts(option写法)
import { defineStore } from 'pinia';
import { store } from '/@/store';
export const useDemoStore = defineStore('demo',{
state:()=>{
return {
name:'张三',
count:0
}
},
getters:{
getName:(state)=>{
// return this.name+'getName';
return state.name+'getName';
return (id)=>state.list.filter(v=>v.id===id);//getters传参
}
},
actions:{
updateName:(val:string)=>{
this.name=val;
}
},
persist:true
});
state:用来存储全局的状态,这边定义的,可以全局访问;
getters:和Vuex一样,来监视或者计算状态变化;具有缓存功能,组件中调用多次,实际在store中只执行了一次;
actions:适合处理修改逻辑复杂的数据,可以在actions中定义好函数,然后在组件中调用,是state数据相关的业务逻辑,需求不同,逻辑不同。
注:getters和actions里可以直接使用this。
5.组件中使用
// src/views/Demo.vue
<template>
<h1>counter:{{counter}}</h1>
<el-button @click="add"> 自增 </el-button>
</template>
<script setup lang="ts">
import { storeToRefs} from 'pinia';
import useDemoStore from '/@/store/modules/demo.ts';
const demoStore = useDemoStore();
const { counter } = storeToRefs(demoStore);
const add = ()=>{
demoStore.increment();
}
console.log(demoStore.getName());
</script>
注:无论是pinia还是vuex,通过解构的方式获取状态,会导致状态失去响应性。如:
const { counter } = demoStore;
此时counter丢失了响应性,当其值发生改变时,其他组件不会监听到。所以pinia提供了storeToRefs函数,使其解构出来的状态仍然具有相应性。
const { counter } = storeToRefs(demoStore);
6.路由中使用
演示在全局路由守卫中获取状态值。创建一个路由守卫,在路由守卫中使用nprogress显示页面加载进度。
1.创建全局路由守卫
- 安装nprogress
npm install nprogress
npm install @types/nprogress -D
- 创建全局路由守卫
// src\router\guard\index.ts
import nProgress from 'nprogress';
import 'nprogress/nprogress.css';
nProgress.configure({
showspinner:false,
})
// 全局前置守卫
router.beforeEach((to,from)=>{
nProgress.start();
return true;
})
// 全局后置守卫
router.afterEach((to,from)=>{
nProgress.done(true);
})
3.在main.ts中引入全局守卫路由
import '@/router/guard/index'
2.全局守卫中使用全局状态
实际开发中,路由切换时,可能需要从全局状态中获取token等信息,判断是否能进入下一页面。
// src\router\guard\index.ts
...
import useDemoStore from '@/store/modules/demo'
import {storeToRefs} from 'pinia'
...
const demoStore = useDemoStore();
const {counter} = storeToRefs(demoStore);
// 全局前置守卫
router.beforeEach((to,from)=>{
nProgress.start();
console.log(counter);
return true;
})
在钩子函数外,pinia还没有挂载,所以会报错,应该修改后如下:
// src\router\guard\index.ts
import router from '@/router';
import nProgress from 'nprogress';
import 'nprogress/nprogress.css';
import useDemoStore from '@/store/modules/demo'
import {storeToRefs} from 'pinia'
nProgress.configure({
showspinner:false,
})
// 全局前置守卫
router.beforeEach((to,from)=>{
nProgress.start();
const demoStore = useDemoStore();
const {counter} = storeToRefs(demoStore);
console.log(counter);
return true;
})
// 全局后置守卫
router.afterEach((to,from)=>{
nProgress.done(true);
})
# 四、Pinia状态持久化
## 1.为什么需要状态持久化?
因为状态存储到浏览器内存中,刷新浏览器后,重新加载页面会重新初始化vue.pinia,导致浏览器存储中的数据丢失。
实际项目中,浏览器刷新时,这有些数据希望保存下来。
解决方案:状态改变时将其同步到浏览器的存储中,如cookie,localStorage,sessionStorage,每次初始化状态时从存储中获取初始值即可。
## 2.插件pinia-plugin-persistedstate
1. npm install pinia-plugin-persistedstate
2. src/store/index.ts
```typescript
import { createPinia } from 'pinia';
import piniaPluginPersistedState from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(piniaPluginPersistedState);
export default pinia;
- 模块管理中的使用:
// src/store/modules/demo.ts
import { defineStore } from 'pinia';
import { store } from '/@/store';
const useDemoStore = defineStore('demo',()=>{
const counter = ref(0);
const increment = ()=>{
counter.value++;
}
return {counter,increment}
},{ persist:true });
export default useDemoStore;
persist : 值为true,状态存储在localStorage中;该localStorage中的key为模块名“demo”。
如何修改key?如何将状态存储到SessionStorage?
{
persist:{
key:"byyourself",
storage:"sessionStorage",
}
}
四、修改数据 $patch
const updataName = () =>{
// 1.修改单个数据
demoStore.name='李四';
// 2.修改多个数据-$patch
demoStore.$patch({
name:'李四',
count:18
)
// 3.修改复杂数据-$patch+函数
demoStore.$patch((state)=>{
state.name='李四';
})
// 4.使用action处理数据
demoStore.updateName('李四')
}
五、重置数据 $reset
const resetName = ()=>{
demoStore.$reset();
}
pinia setup 方式构建$reset方法失效解决方案:
import { creatPinia } from 'pinia';
const pinia = creatPinia();
pinia.use((store)=>{
const initialStata=JSON.parse(JSON.stringify(store.$state));
store.$reset=()=>{
store.$patch(initialStata)
}
})
六、监听数据 $subscribe
const subscribeName = demoStore.$subscribe((mutations,state)=>{
// 监听store中的某个值变化,处理一些逻辑
// $subscribe第一个参数mutations,包含了3个属性:
// 1.events:改变的具体数据,newValue,oldValue;
// 2.storeId:当前store的Id,这里是'demo';
// 3.type:记录改变数据途径(direct:通过actions改变;patch object:
// 提供$patch传递的方式改变的;patch function:通过$patch传递函数)
// $subscribe第二参数,是options对象,是各种参数配置
// imemediate,deep和vue2中的watch里的参数一样;
// detached:默认是false,正常情况下,当订阅所在的组件被卸载时,订阅失败;
// 当值为true时,即便订阅的组件被卸载,订阅依然生效;
},{
imemediate:false,
deep:false,
detached:false
})
// 停止订阅监听
subscribeName();
七、监听Actions $onAction()
const subAction=demoStore.$onAction((name,store,args,after,onError)=>{
// name: actions的名字;
// store: store实例;
// args: 调用这个actions用的参数;
// after((result)=>{
// // 在action执行完毕后执行的逻辑
// })
// onError((result)=>{
// // 在action执行异常后执行的逻辑
// })
},true)// true组件卸载后,监听依旧保留
// 停止监听Actions
subAction();
Tip:可以在pinia实例上使用watch()函数侦听整个state。
watch(pinia.state,(state)=>{
// 每当状态发生变化时,将整个state持久化到本地存储
localStorage.setItem(,JSON.stringify(state))
},}{deep:true})
Tip:不使用setup时。
import { mapState, mapActions } from 'pinia';
import { mainStore } from '@/store/index.ts'
export default {
computed:{
...mapState(mainStore,['name','count','getName']),
// 定义
myOwnName:"name"
},// 可以在组件中通过this.name访问
methods:{
...mapActions(mainStore,['updateName']),d
// 定义
myOwnName:"updateName"
},// 可以在组件中通过this.updateName()调用
}
}