Pinia 是对vuex 5 提案的测试,并以取得成功。Pinia 的作者 (Eduardo) 是 Vue.js 核心团队的一员。对这个项目的意图是重新设计使用全局 Store 的体验。
1. Pina介绍
-
pinia无需创建复杂的包装器来支持typescript,对于typescript类型判断是天然支持的,享受ide带来的自动补全,减少开发的心智负担
-
减去了mutations的概念,只保留state,getters和anctions三个概念,减少代码冗余
-
无需手动添加store,创建的store会在使用时自动添加
-
没有模块module的概念,不用面对一个store下嵌套着许多模块,使用单文件store,可以直接导入其他store使用
-
支持服务端渲染
2. Pinia安装
首先我们初始化一个vite+vue+ts工程
npm create vite pinia-demo -- --template vue-ts
2.1 安装pinia
npm i pinia@next
2.2 在入口文件中把Pinia挂载为Vue插件
打开项目,编辑src目录下的mian.ts文件,引入Pinia
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')
2.3 在文件中声明store
在src目录下创建一个store文件夹用来存放状态管理,然后新建一个counter.ts,我们来做一个简单的计数器状态应用
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter',{
state: () => {
return {
count: 0,
}
},
...
})
或写成如下
import { defineStore } from 'pinia'
export const useCounterStore = defineStore({
id: 'counter',
state: () => {
return {
count: 0,
}
},
...
})
2.4 在组建中引入,并使用store
<script setup lang="ts">
import { useCounterStore } from './store/counter'
const useCounter = useCounterStore()
</script>
<template>
<h2>{{ useCounter }}</h2>
<h2>{{ useCounter.count }}</h2>
<h2>{{ useCounter.doubleCount() }}</h2>
<button @click="useCounter.increment">increment</button>
</template>
3. Store中的state
Pinia通过defineStore函数来创建一个store,它接收一个id用来标识store,以及store选项
3.1 在store中声明state
export const useStore = defineStore({
id: "main",
// 第一种
state(){
return {
counter: 1,
name: "main"
}
}
// 第二种
state: ()=>({
counter: 45,
name: 'zs'
}),
3.2 在模版组件中使用
在组建中引用,创建便可访问sotre中的state。
<script setup lang="ts">
import {useStore} from "./store/counter"
let store = useStore()
</script>
<template>
<div>{{store.counter}}</div>
</template>
3.3 解构store
<script setup lang="ts">
import {useStore} from "./store/counter"
// 直接解构数据变为非响应式
// let {counter} = useStore()
import { storeToRefs } from 'pinia'
// 通过storeToRefs转换为响应式对象解构可正常使用
let {counter} = storeToRefs(useStore())
</script>
<template>
<div>{{counter}}</div>
</template>
4. Store 中的getters
getters 可以理解为vue中的计算属性,getters的第一个参数是state。
4.1 声明 getters
export const useStore = defineStore({
id: "main",
...
getters: {
doubleCounter(state){
return state.counter * 2
},
}
})
使用与state相同
4.2 访问当前store中的的其他getters
export const useStore = defineStore({
id: "main",
...
getters: {
doubleCounter(state){
return state.counter * 2
},
}
})
4.3 getters传递参数
利用高阶函数传递参数
export const useStore = defineStore({
id: "main",
...
getters: {
doubleCounterPlus(state){
return (num)=> {
return state.counter * 2 + num
}
},
}
})
<div>{{dobuleCounterPlus(3)}}</div>
4.4 访问其他store中的getters
创建 defalut store,并将其引入使用
export const useDefaultStore = defineStore({
id: "default",
state: ()=>({
time: 2
}),
getters: {
calcTime(state){
return state.time + 2
}
}
})
export const useStore = defineStore({
id: "main",
...
getters: {
addDefault(state){
const store = useDefaultStore()
return state.counter + store.calcTime
}
}
})
5. Store中的Actions
5.1 同步使用
pinia 中不再区分Mutaions与Actions,同步异步均使用 Actions
export const useDefaultStore = defineStore({
id: "default",
...
actions: {
addCounter(num: number){
this.counter = this.doubleCounter + num
}
}
})
5.2 异步使用
export const useDefaultStore = defineStore({
id: "default",
...
actions: {
addCounter(num: number){
setTimeout(() => {
this.counter = this.doubleCounter + num
}, 3000)
}
}
})
5.3 使用其他 store 的Actions
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
5.4 订阅 Actions
store.$onAction()({
name, // name of the action
store, // store instance, same as `someStore`
args, // array of parameters passed to the action
after, // hook after the action returns or resolves
onError, // hook if the action throws or rejects
}) => {
...
}
})
6. 其他
6.1 修改state
store.counter++
// 修改多个属性
store.$patch({ count: store.counter + 1 })
// 可以重新赋值整个state
store.$state = { counter: 666, name: 'Paimon' }
观察state
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// same as cartStore.$id
mutation.storeId // 'cart'
// only available with mutation.type === 'patch object'
mutation.payload // patch object passed to cartStore.$patch()
// persist the whole state to the local storage whenever it changes
localStorage.setItem('cart', JSON.stringify(state))
})
6.2 对optionsapi组件支持
export default {
computed: {
...mapStores(useCounterStore, useUserStore)
...mapState(useCounterStore, ['count', 'double']),
// 利用mapWritableState,能够写入这些状态属性
...mapWritableState(useStore, ['counter'])
},
methods: {
...mapActions(useCounterStore, ['increment']),
},
}
6.3 $reset() 👏
通过$reset()方法还原最初sotre
const store = useStore()
store.$reset()
7. 插件
- 向stores增加属性
- 向stores中增加新的options
- 向stores增加新的方法
- 包装现有方法
- 修改或取消 actions
- 实施副作用如本地存储
- 仅适用于特定 store
export function myPiniaPlugin(context) {
context.pinia // the pinia created with `createPinia()`
context.app // the current app created with `createApp()` (Vue 3 only)
context.store // the store the plugin is augmenting
context.options // the options object defining the store passed to `defineStore()`
// ...
}
pinia.use(myPiniaPlugin)