Pinia
在Vue3中,你仍然可以使用Vuex,但是官方文档建议使用Pinaia
,应用在服务端渲染时,Pinia
比Vuex
更加安全。
相比于Vuex
,Pinia
少了Mutations
安装和使用
安装:
npm install pinia
使用pinia
main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
核心概念
Store
Store管理着全局状态,和Vuex中一样的概念。在store中,可以通过this
访问当前的store实例
请注意,每个Store都被
reactive
包装过,其中的响应式内容可以被自动解包。Store的实例化应该在pinia
创建之后
定义Store
使用defineStore()
定义,第一个参数是store的唯一ID(命名是要保证独一无二),Pinia
用这个ID来连接store
和devtools
,第二个参数是store
的配置,可以是一个Setup函数
或者一个Options
对象
import { defineStore } from 'pinia'
// 官方建议是对返回值用 useXxxStore 的格式进行命名
export const useMyStore = defineStore('myStrore', {
// 像vuex中一样,引入各种各样的store模块,比如:
/*
* useUserStore,
* usePetStore,
* usePermisstionStore,
*/
})
defineStore()
中可以传入一个Options对象来定义一个store,这种方式非常像Vuex
// 默认暴露
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
account: 11111,
passwords: 15555,
score: 0,
}),
getters: {
level: (state) => {
state.score>60 ? 'A' : 'B'
}
},
actions: {
// actions中可以通过this访问本store中的数据
addScore(){
this.score += 10
}
}
})
defineStore()
中可以传入一个setup函数
来定义store,在这个函数中,ref()
相当于state
,computed()
相当于getters
,function()
相当于methods
。传入创建store
显然更加灵活
import { defineStore } from 'pinia'
export const uesCityStore = defineStore('city', () => {
const peopleData = ref(0)
const weather = ref('晴')
function increment(){
//使用ref()响应式声明的状态,应当使用其value属性来改变值
peopleData.value++
}
// 声明的都要 return
return { peopleData, weather, increment }
})
引入和使用Store
引入后需要将其实例化,store
是一个reactive
包裹的对象,不要直接对state
解构赋值,那将破坏响应性。可以对actions
直接解构
<script setup>
import { useUserStore } from '@/stores/user'
// 实例化 store
cosnt userStore = useUserStore()
// 然后在该组件中就可以通过 userStore 调用 useUserStore
setTimeOut( ()=>{
store.addScore()
console.log(store.score)
}, 1000)
</script>
如果想要解构复制state
同时保持其响应性,那么需要调用storeToRefs()
import { storeToRefs } from 'pinia'
const { account, passwords } = storeToRefs(userStore)
State
state
就像是data
,Pinia中,state被定义为一个返回初始状态的函数
创建State
JS:
import { defineStore } from 'pinia',
const useStore = defineStore('test', {
state: () => {
return {
// 里面写法就和 Vuex 一样了
}
}
})
TS
// 使用接口定义 state, 并设置 state() 的返回值类型
// 先定义数据类型的接口
interface UserInfo {
id: Number
account: String
passwords: String
}
//然后定义State的接口
interface State {
userList: UserInfo[]
user: UserInfo | null
}
//创建store
const useStore = defineStore('test', {
// 使用State接口
state: (): State => {
return {
userList: [],
user: null
}
}
})
或者可以不定义State接口,让Pinia自动推断state的类型
interface UserInfo {
id: Number
account: String
passwords: String
}
const useUserStore = defineStore('test', {
state: () => {
return {
userList: [] as UserInfo[], // 设置初始值和类型
user: null as UserInfo | null
}
}
})
访问State
可以直接用store访问: userStore.user
重置State
调用store
的 $reset() 方法
userStore.$reset()
mapState()
Pinia
同样支持mapState()
辅助函数,它会将state
映射为只读属性,使用格式和vuex
一样
在组件中修改state中的状态名
使用mapWritableState()
<script setup>
import { mapWritableState } from 'pinia'
import { useUserStore } from '@/stores/user'
import { computed } from 'vue'
const msg = computed(() => {
return {
//这种方式引入的state状态名没有变化,可以通过 this.account 访问useUserStore中的account
...mapWritableState(useUserStore, ['account', 'passwords'])
//这种方式下引入的state状态名发生了改变,在该组件下需要使用 this.ac 访问useUserStore中的account
...mapWritableState(useUserStore, {
ac: 'account'
})
}
})
</script>
批量变更State状态
在Pinia
中,可以直接通过store变更state, 也可以使用store.$patch()
进行状态的变更
变更有两种方法,第一种是传入一个配置项
userStore.$patch({
account: '600600',
passwords: 'asdasdads',
id: userStore.id==1 ? 0 : 1
})
第二种是传一个方法,也是常用的
userStore.$patch((state) => {
state.userList.push({ account: '45485454', passwords: 'asdasfas', id: 6 })
})
初始化整个app的state
pinia.state.value = {}
订阅state
使用$subscribe()
方法监听state
的变化,每当state
发生变化时触发其中的回调。它需要接收一个带有参数为(mutation, state)
的方法,和一个可选项detached
来设置是否在组件被卸载后还要保留该订阅
userStore.$subscribe((mutation, state) => {
// ...
},
{ detached: true }
)
Getter
Getter
就像是computed
,你需要使用getters
属性去定义
创建和访问
你可以使用箭头函数或者匿名函数去创建
export const userStore = defineStore('test', {
getters: {
newId: (state) => {
state.id = 30
},
role(){
// 使用 this 访问同一 store 中的其他 getter
return this.newId%2==0 ? 'admin' : ''
},
// TS中必须定义返回类型
newIdByTs(): number {
return this.id++
}
}
})
组件中访问: userStore.newId
可以在组件中给getter
传参
<script setup>
import { useUserListStore } from './store'
const user = useUserStore()
const { getUserId } = storeToRefs(user)
// 请注意,你 应该使用 `getUserId.value` 来访问 <script setup> 中的函数
</script>
<template>
<p>UserId: {{ getUserId(6) }}</p>
</template>
在getter中缓存结果
在getters
中缓存结果,有时可以提高getter的性能
export const useUserStore = defineStore('test', {
getters: {
getActiveUserById(state) {
const xxx // xxx是缓存在getter中的东西
return func(xxx)
},
},
})
在getter中访问其他store的getter
把别的store import进来实例化然后正常用就行了
import { otherStore } from 'xxx'
export const userStore = defineStore('test', {
getters: {
otherGetter: (state) => {
const otherStore = otherStore()
// ...正常用就行
}
}
})
Action
actions
就像是methods
定义和调用
创建和Vuex一样,里面的函数也是,actions中的方法都可以是异步的,在里面可以使用await
、this
调用actions
中的方法
// 不论是js中还是模板中都是
yourStoreName.functionsInActions(params)
访问其他store中的actions
类比在getters中调用其他store的getter
订阅actions
使用stiore.$onAction(callback, Boolean)
监听actions的结果并执行回调。他有几个回调的执行时刻:
after
: 在Promise
解决后,允许在action解决后执行一个回调onError
: 允许在action抛出错误或者 Promise reject 时执行一个回调
import { yourStore } from 'xxx'
const yourStore = yourStore()
// 在store实例上绑定一个用于当前组价中的actions监听器
const unsubscribe = yourStore.$onAction(
(params) => {
// 执行一些回调,总会触发
//当action成功运行完成后触发,after()接收action返回的任何promise
after((result) => {
// ...
})
//若action抛出一个错误或者返回一个reject的Promise时触发
onError((err) => {
// ...
})
},
true //可选项, 设置true, 那么在使用这个订阅器的组件卸载后,该订阅器依然会被保留
)
// 移除监听
unsubscribe()
Pinia 扩展插件
我们可以写一个函数将他作为插件,然后使用pinia.use()
全局挂载到Pinia
实例上,然后他会作为每一个store
的新属性而存在。在一个插件中, state 变更或添加(包括调用 store.$patch()
)都是发生在 store 被激活之前,因此不会触发任何订阅函数。
import { createPinia } from 'pinia'
const pinia = createPinia()
function myPlugin(){
// ...
return ...
}
// 全局挂载插件
pinia.use(myPlugin)
// 或者这样,建议传入对象,这杨能够被devtools自动追踪到
pinia.use(({ params }) => {
// ...
})
// 其他文件中使用
const yourStore = youStore()
yourStore.myPlugin()
添加新的外部属性
当添加外部属性、第三方库的类实例或者非响应式的简单值时,需要先用vue
提供的markRaw()
将其包装,再将它传给pinia
import { markRaw } from 'vue'
import { yyy } from 'xxx' //外部属性、第三方库的类实例或者非响应式的简单值
pinia.use(({ store }) => {
store.yyy = markRaw(yyy)
})
插件中可以使用store.$subscribe()
、store.$onAction()
添加新选项
定义store
时可以创建新选项,创建的新选项会被插件读取并使用
创建一个防抖选项实现所有的action防抖
const yourStore = defineStore('storeId', {
actions: {
sendMsg(){
// ...
}
},
debounce: {
sendMsg: 400, // 让sendMsg这个action防抖400ms
}
})
// 使用任意防抖库
import debounce from 'lodash/debounce'
pinia.use(({ options, store }) => {
if (options.debounce) {
// 用新的 action 来覆盖旧的action
return Object.keys(options.debounce).reduce((debouncedActions, action) => {
debouncedActions[action] = debounce(
store[action],
options.debounce[action]
)
return debouncedActions
}, {})
}
})
TS中使用
组件外的Store
单页面应用中使用
如果想在路由器中使用store,那么要把store实例化放在router.beforeEach()
内,因为那个时候Pinia
已经被安装。由此可知,你应该在Pinia
被安装后再实例化store