参考文章1:上手 Vue 新的状态管理 Pinia,一篇文章就够了
参考文章2:
作者:南山种子外卖跑手
链接:https://juejin.cn/post/7089032094231298084
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Pinia是一个全新的Vue状态管理库,是Vuex的代替者(可以理解成Vuex5,Vuex不会再更新)。
Pinia和Vuex
Vuex | Pinia |
---|---|
State 、Gettes 、Mutations (同步)、Actions (异步) |
State 、Gettes 、Actions (同步异步都支持) |
Vuex4用于Vue3,Vuex3用于Vue2 | Vue2和Vue3都支持 |
Pinia的其他特点
Vue DevTools
mutation 已被弃用,初衷是带来 devtools 的集成方案
某项目有3个store「user、job、pay」,另外有2个路由页面「首页、个人中心页」,首页用到job store,个人中心页用到了user store,分别用Pinia和Vuex对其状态管理。
先看Vuex的代码分割: 打包时,vuex会把3个store合并打包,当首页用到Vuex时,这个包会引入到首页一起打包,最后输出1个js chunk。这样的问题是,其实首页只需要其中1个store,但其他2个无关的store也被打包进来,造成资源浪费。
Pinia的代码分割: 打包时,Pinia会检查引用依赖,当首页用到job store,打包只会把用到的store和页面合并输出1个js chunk,其他2个store不耦合在其中。Pinia能做到这点,是因为它的设计就是store分离的,解决了项目的耦合问题。
安装pinia npm install pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'; //引入createPinia
import App from'./App.vue' //引入根组件
const pinia = createPinia() //创建pinia实例
const app = creatApp(App) //创建Vue应用实例
app.use(pinia)//安装pinia插件
app.mount('#app')
PiniaVuePlugin
插件import { createPinia,PiniaVuePlugin } from 'pinia';
Vue.use(PiniaVuePlugin)
const pinia = createPinia() //创建pinia实例
new Vue({
router,
store,
render: h => h(App),
pinia
}).$mount('#app')
defineStore()
方法
Option对象
对应options API的写法
state
返回初始状态的函数。必须是箭头函数,箭头函数有利于TS类型推导。必须是函数的原因是防止服务端渲染时交叉请求导致数据状态污染(客户端渲染没有任何区别)getters
就是用来封装计算属性,类似于组件的computed,有缓存功能actions
就是用来封装业务逻辑,类似与组件的methods,修改 stateSetup函数
对应composition API的写法
ref()
是 state
属性,用于存储容器store
里的数据computed()
是 getters
function
是action
,修改 statestore
容器实例Pinia会把所有的容器挂在到根容器
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counterForOptions', {
state: () => {
return { count: 1 };
},
actions:{
changeState(){ //通过this访问容器里的数据
this.count++
}
}
getters: {
//参数state是状态数据,可选参数
doubleCount(state) {
return state.count * 2;
}
doubleCount1(state):number { //也可以使用this,但是类型推导存在问题,必须手动指定返回值类型
return this.count * 2;
}
}
});
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counterForSetup', () => {
const count = ref<number>(1);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
return { count, doubleCount, increment };
});
调用 defineStore()
返回的函数时创建store
实例,store
实例是一个被reactive
包装的对象
//组件内使用
<script setup>
//useCounterStore接收defineStore返回的函数
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useCounterStore()
</script>
注意:组件外使用时,必须在函数内部
import { useAuthUserStore } from '@/stores/auth-user'
router.beforeEach((to, from, next) => {
//因为路由器是在其被安装之后开始导航的
// 必须再函数内部使用,为确保 pinia 实例被激活
const authUserStore = useCounterStore()
if (authUserStore.loggedIn) next()
else next('/login')
})
直接解构后的count变量会失去响应式,成为一次性数据。
//组件中的代码
<script setup lang="ts">
import {useMainStore} from '../store'
const {count} = useMainStore()
</script>
<template>
<div>{{count }}</div>
</template>
解决办法:使用storeToRefs()
方法,该方法的作用将解构出来的数据做ref
响应式代理
storeToRefs()
方法
toRef
和toRefs
实现的一个 api 方法<script setup lang="ts">
import {useMainStore} from '../store'
import {storeToRefs} from 'pinia'
const {count} = storeToRefs(useMainStore())
/*ObjectRefImpl
{
"_object": {
"count": 1
},
"_key": "count",
"__v_isRef": true
}
*/
console.log(count)
console.log(count.value) //1
</script>
store的$patch()
:批量更新state
- 参数可以是对象和函数(参数是state)
<script setup lang="ts">
import {useMainStore} from '../store'
import {storeToRefs} from 'pinia'
const mainStore = useMainStore()
const {count} = storeToRefs(useMainStore())
const changeCount = ()=>{
//方式1:最简单的方式
// mainStore.count++;
//方式2:如果需要多个数据,建议使用$patch,批量更新
//mainStore.$patch({
// count:mainStore.count+1,
//...数据名:修改后的值
//涉及数组很麻烦
// })
//方式3:$patch(函数)其中函数的参数是state就是store的state,批量更新
//mainStore.$patch(state=>{
// state.count++
//})
//方法4:逻辑比较多的时候可以封装到actions做处理,
mainStore.changeState()
}
</script>
也可以直接从store
中结构action
,因为action也被绑定在store上
<script setup lang="ts">
import {useMainStore} from '../store'
const mainStore = useMainStore()
const {changeState} = store
</script>
//虽然使用了三次,但是只会调用一次,有缓存功能
<template>
<div>
<div>{{mainStore.count }}</div>
<p>
<button @click="changeCount">修改数据</button>
</p>
<p>{{mainStore.doubleCount}}</p>
<p>{{mainStore.doubleCount}}</p>
<p>{{mainStore.doubleCount}}</p>
</div>
</template>
mapStores()
mapState()
:将 state
属性映射为 只读的计算属性mapWritableState()
mapActions()
1.不再使用 mapMutations。
2.Pinia为了兼容option api 提供的类似Vuex map 系列的映射辅助函数,不推荐使用。
3. mapGetters = mapState,mapGetters的底层实现逻辑和mapState一样
mapState():将 state
属性映射为 只读的计算属性
使用数组直接 同名映射:…mapState(store, [‘count’])
使用 对象 可映射为 新的名称:…mapState(store, { key: value, fn (state) })
使用对象时, value 值可以是 字符串,可以是 函数;
对象内部也可以直接定义 函数,接收 store 作为参数