文章目录
Pinia学习笔记
参考文章1:上手 Vue 新的状态管理 Pinia,一篇文章就够了
参考文章2:
作者:南山种子外卖跑手
链接:https://juejin.cn/post/7089032094231298084
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
简介
存在问题:数据不能持久化
Pinia是什么
Pinia是一个全新的Vue状态管理库,是Vuex的代替者(可以理解成Vuex5,Vuex不会再更新),可以实现任意组件之间通信。
Pinia和Vuex
Vuex | Pinia |
---|---|
state 、getters 、mutations (同步)、actions (异步)、modules | state 、getters 、actions (同步异步都支持) |
Vuex4用于Vue3,Vuex3用于Vue2 | Vue2和Vue3都支持 |
Pinia的其他特点
- 提供扁平结构,每个store都是互相独立的。所以pinia具有更好的代码分割且没有命名空间,也可以通过一个模块中导入另一个模块来隐式嵌套store。
- Vue2 和 Vue3 都支持,除了初始化安装和SSR配置之外,两者使用上的API都是相同的
- 支持
Vue DevTools
- 模块热更新
- 无需重新加载页面就可以修改模块
- 热更新的时候会保持任何现有状态
- 支持使用插件扩展 Pinia 功能
- 支持服务端渲染
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分离的,解决了项目的耦合问题。
1.挂载Pinia
安装pinia npm install pinia
Vue3
createPinia
:创建大仓库(根容量),大仓库可以管理小仓库
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')
Vue2:安装PiniaVuePlugin
插件
import { createPinia,PiniaVuePlugin } from 'pinia';
Vue.use(PiniaVuePlugin)
const pinia = createPinia() //创建pinia实例
new Vue({
router,
store,
render: h => h(App),
pinia
}).$mount('#app')
2.定义store的两种方式options API 和 composition API
defineStore()
:
- 第一个参数是表示store的唯一名称id,Pinia 会把所有的模块都挂载到根容器上
- 第二个参数可接受两类值:Setup 函数或 Option 对象。
- 第二个参数是
Option对象
对应options API的写法state
返回初始状态的函数。必须是箭头函数,箭头函数有利于TS类型推导。必须是函数的原因是防止服务端渲染时交叉请求导致数据状态污染(客户端渲染没有任何区别)getters
就是用来封装计算属性,类似于组件的computed,有缓存功能actions
就是用来封装业务逻辑,类似与组件的methods,修改 state
- 第二个参数是
Setup函数
对应composition API的写法ref()
是state
属性,用于存储容器store
里的数据computed()
是getters
function
是action
,修改 state
- 第二个参数是
- 返回值是一个函数,该函数调用后返回
store
容器实例(小仓库)
Pinia会把所有的容器(小仓库)挂在到根容器(大仓库)
使用options API模式定义
// 创建小仓库
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;
}
}
});
使用composition API模式
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 };
});
2.业务组件对store的使用
创建store实例
调用 defineStore()
返回的函数时创建store
实例,store
实例是一个被reactive
包装的对象
store
实例(小仓库)示例,数据被绑定在该实例上
//组件内使用
<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')
})
解构访问Pinia容器数据
直接解构后的count变量会失去响应式,成为一次性数据。
//组件中的代码
<script setup lang="ts">
import {useMainStore} from '../store'
const {count} = useMainStore()
</script>
<template>
<div>{{count }}</div>
</template>
解决办法:使用storeToRefs()
方法,该方法的作用将解构出来的数据做ref
响应式代理
storeToRefs()
方法
- 作用是创建一个引用方法。包含 store 的所有 state、 getter 和 plugin 添加的 state 属性,会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
- 底层使用
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>
状态更新和Actions
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>
getters使用
//虽然使用了三次,但是只会调用一次,有缓存功能
<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>
Pinia和VueDevtools
映射辅助函数
mapStores()
mapState()
:将state
属性映射为 只读的计算属性mapWritableState()
: 将state
属性映射为 可修改的计算属性,类似mapState()
,区别是第二个参数不可以传递函数mapActions()
1.不再使用 mapMutations。
2.Pinia为了兼容option api 提供的类似Vuex map 系列的映射辅助函数,不推荐使用。
3. mapGetters = mapState,mapGetters的底层实现逻辑和mapState一样
mapState():将 state
属性映射为 只读的计算属性
- 使用数组直接 同名映射:
…mapState(store, [‘count’])
- 使用 对象 可映射为 新的名称:
…mapState(store, { myOwnName: stateValue| fn (state) })
- 使用对象时,
value
值可以是 字符串,可以是函数; - 对象内部也可以直接定义 函数,接收
store
作为参数
- 使用对象时,
import {mapState} from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
computed: {
// 生成的计算属性名字为count,值等于 store.count
...mapState(useCounterStore, ['count'])
// 第二个参数是对象的写法
...mapState(useCounterStore, {
myOwnName: 'count',
// 你也可以写一个函数来获得对 store 的访问权
double: store => store.count * 2,
// 它可以访问 `this`,但它没有标注类型...
magicValue(store) {
return store.someGetter + this.count + this.double
},
}),
},
}