前言
Pinia简称小菠萝🍍,是一个专为Vue 3设计的现代化状态管理库,为Vue 3
开发的,它提供了一种简单、可扩展和类型安全的方式来管理应用程序的状态。
与Vue 2
中的Vuex相比,Pinia
提供了更好的TypeScrip
t支持,具有更好的类型定义和类型推断,可在编译时捕获错误,提供更高的代码可靠性和开发体验。它是专为Vue 3
设计的,充分利用了Vue 3
的新特性,如Composition API
,以提供更直接、自然和灵活的状态管理体验。Pinia
的核心概念是Store
,它类似于Vuex
中的模块,用于管理应用程序的状,可以将相关的状态和逻辑组合到单个Store
中,使代码更清晰、结构更有组织性。除此之外海提供了许多有用的特性和功能,例如模块化组织、状态持久化、插件扩展等。
总的来说,Pinia
是一个功能强大而灵活的状态管理解决方案,适用于各种规模的Vue 3
应用程序。它提供了现代化的特性和工具,帮助我们更好地组织、管理和扩展应用程序的状态,同时提供了更好的类型安全和开发体验。
安装
运行安装命令
npm install pinia
在main.ts
中引入
// main.ts
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
新建stores
文件,用于存放所有的store
,然后创建index.ts
。
同过 defineStore()
定义一个store
,它接受一个参数作为仓库名称,也就是Id
。它返回一个函数,默认我们使用user
开头的风格来接收。第二个参数为一个Setup
函数或者Option
对象。
import { defineStore } from 'pinia'
export const useUsersStore = defineStore('users', {
// 其他配置...
})
Option Store
这种方式熟悉Vuex
的很了解,传入一个带有 state
、actions
与 getters
属性的 Option
对象
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100
}
},
getters: {
getName: (state) => state.name + '🐔你好帅'
},
actions:{
getUserInfo {
...
}
}
})
在 Option Store
中:
state
是store
的数据data
getters
是store
的计算属性computed
actions
则是方法methods
Setup Store
和Vue3 Composition API组合式API
里setup
函数相似,传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想要暴露出去的属性和方法的对象。
export const userUsersStore = defineStore('users', () => {
const name = ref('inkun')
function getInkun() {
getInkun.value + '🐔你好帅'
}
return { name, getInkun }
})
在 Setup Store
中:
ref()
就是state
属性computed()
就是getters
function()
就是actions
使用Store
定义一个store
后,在组件里引入这个store
然后就行使用,不需要像ref
一样使用.value
,可以直接修改访问。
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useCounterStore()
</script>
State
state
定义一个返回初始状态的函数,函数内返回一个对象,里面是需要定义的数据。
对于基础类型而言,[[../TypeScript|TypeScript]]可以自行推断出它们的数据类型,也可以接口,定义state
函数返回值。
interface State {
userList: UserInfo[]
user: UserInfo | null
}
interface UserInfo {
name: string
age: number
}
export const userUsersStore = defineStore('users', {
state: (): State => {
return {
userList: [],
user: null
}
}
})
修改State
默认情况下可以直接通过store
实例访问state
,并且可以直接对其进行读写操作。
在Vuex
中,如果要对state
进行修改必须要定一个mutation
,通过mutation
进行提交,太过于繁琐。
const store = useStore()
store.count++
变更
除了用 store.count++
直接改变 store
,还可以调用 $patch
方法。它允许你用一个 state
的补丁对象在同一时间更改多个属性:
store.$patch({
count: store.count + 1,
name: 'ff',
})
重置
可以通过调用 store
的 $reset()
方法将 state
重置为初始值。
const store = useStore()
store.$reset()
监听
类似于 Vuex
的 subscribe
方法,可以通过 store
的 $subscribe()
方法侦听 state
及其变化。
store.$subscribe((mutation, state) => {
mutation.storeId // 'cart'
console.log('state change', state)
console.log('mutation', mutation.type) // 'direct' | 'patch object' | 'patch function'
console.log('mutation2', mutation.storeId) // 'users'
// 只有 mutation.type === 'patch object'的情况下才可用
// mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
console.log('mutation3', mutation.payload)
}, {
detached: true
})
默认情况下,state subscription
会被绑定到添加它们的组件上,当该组件被卸载时,它们将被自动删除。如果想在组件卸载后依旧保留它们,将 { detached: true }
作为第二个参数,以将 state subscription
从当前组件中分离,此时组件卸载后,订阅器还可以使用。
结构State
在使用state
时是不允许直接从store
中结构数据,这样会导致数据失去响应式和props
一样。
解构出来的数据是可以正常访问,当数据修改时是不会发生任何变化。
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const {current, name} = useCounterStore() // 数据不会发生变化
function change() {
store.current++
}
</script>
解决方案是通过storeToRefs
将数据重新变回响应式。
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const store= useCounterStore() // 数据不会发生变化
const {name, current} = storeToRefs(store)
function change() {
store.current = 1
name.value = 'ff'
}
</script>
Getter
getter
相当于计算属性,接收一个函数,函数参数为当前store
里的state
,也可以通过this
去访问。
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100
}
},
getUserName(state) {
return state.name + '🐔你好帅'
},
getName(): string {
return this.name + '🐔你实在太帅'
}
})
然后就可以通过store
实例访问getter
<template>
{{ store.getUserName }}
{{ store.getName }}
</template>
<script setup lang="ts">
import { userUsersStore } from './stores'
const store = userUsersStore()
</script>
访问其他Getter
通过this
可以访问其他的getter
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100
}
},
getUserName(state) {
return '大家好,我是' + state.name
},
getName(): string {
return this.getUserName + '🐔你实在太帅'
}
})
向Getter传递参数
getter
只是幕后的计算属性,所以不可以向它们传递任何参数。不过,可以从 getter
返回一个函数,该函数可以接受任意参数:
export const userUsersStore = defineStore('users', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>
访问其他Store里的Getter
将要访问的store
引入并实例就可以
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
action
action
相当于method
,和Vuex
不同的是它异步同步都可以定义。
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100
}
},
actions:{
async getUserInfo {
...
}
}
})
和getter
一样,也可以通过this
访问state
数据
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100
}
},
actions:{
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
}
})
在模版上也是和其他一样通过store
直接访问。
<template>
<button type="button" @click="getUserInfo">获取</button>
</template>
监听
可以通过store.$onAction()
来监听 action
和它们的结果。第一个参数为回调函数,可以获取action 的一些信息,第二个参数如果想在组件卸载后依旧保留它们,将
true作为第二个参数传递给
action` 订阅器。
它返回一个函数,可以在必要的时候调用函数,此时会删除订阅器取消监听。
<script setup lang="ts">
import { userUsersStore } from './stores'
const store = userUsersStore()
const unsubscribe = store.$onAction(({ name, store, args, after, onError }) => {})
// 取消监听
unsubscribe()
</script>
数据持久化
和Vuex
一样,都存在刷新后数据就会丢失,可以通过pinia-plugin-persistedstate插件来解决。
通过在将数据存储到本地storage
中,避免数据刷新丢失。储存位子有两个一个是LocalStorage
和SessionStorage
,具体看个人情况使用。
针对存储的位置,在使用的时候需要考虑项目是否真的要存储在某个位置,合理使用。不能说将用户头像、名称等信息存储在SessionStorage
中,网站关闭后数据也还是会丢失。也不能说将IM
聊天室消息、所有用户信息等数据存储在LocalStorage
中,存储的大小也有限制,这是时候就要使用IndexDB
、web SQL
等方式。所以需要结合项目功能情况。合理选择存储,而不是一股脑的使用。
安装
npm i pinia-plugin-persistedstate
将插件添加到pinia
实例上
// main.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
使用
在创建store
时,设置persist: true
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 1
}
},
getters: {
...
},
actions: {
...
},
persist: true
})
设置完后可以在网页中看到数据存储在localStorage
中
配置persist
persist
可以接收一个对象
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 1
}
},
persist: {
key: 'my-custom-key',
storage: sessionStorage,
paths: ['current'],
serializer: {
deserialize: parse,
serialize: stringify,
},
beforeRestore: (ctx) => {
console.log(`即将恢复 '${ctx.store.$id}'`)
},
afterRestore: (ctx) => {
console.log(`刚刚恢复完 '${ctx.store.$id}'`)
},
}
})
key
: 用于引用storage
中的数据,默认使用store
中的Id
storage
:数据存储位置,默认localStorage
,可以该为sessionStorage
paths
:指定state
中哪些数据需要持久化serializer
:指定持久化时所使用的序列化方法,以及恢复store
时的反序列化方法。beforeRestore
:该hook
将在从storage
中恢复数据之前触发,并且它可以访问整个PiniaPluginContext
,这可用于在恢复数据之前强制地执行特定的操作。afterRestore
:该hook
将在从storage
中恢复数据之后触发,并且它可以访问整个PiniaPluginContext
,这可用于在恢复数据之后强制地执行特定的操作。
全局配置
使用全局配置,就不用单独在每个store
里面做配置,在使用pinia use
的时候就可以通过createPersistedState
函数设置。
// main.ts
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(
createPersistedState({
storage: sessionStorage,
paths: ['current'],
})
)
createPersistedState
里的配置会将每个申明persist: true
的store
添加上配置,但是每个单独store
里的配置将会覆盖调全局声明中的对应项。
全局配置支持一下属性:
- storage
- serializer
- beforeRestore
- afterRestore
启用所有 Store 默认持久化
该配置将会使所有 store
持久化存储,且必须配置 persist: false
显式禁用持久化。
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(
createPersistedState({
auto: true,
})
)
Store多个持久化配置
在一些特殊情况下,每个store
中的数据存储的位置不一样,可以将persist
设置为接收多个配置形式。
import { defineStore } from 'pinia'
defineStore('store', {
state: () => ({
toLocal: '',
toSession: '',
toNowhere: '',
}),
persist: [
{
paths: ['toLocal'],
storage: localStorage,
},
{
paths: ['toSession'],
storage: sessionStorage,
},
],
})
强制恢复数据
每个 store
都有 $hydrate
方法来手动触发数据恢复。默认情况下,调用此方法还将触发 beforeRestore
和 afterRestore
钩子。但是可以通过配置方法来避免这两个钩子触发。
import { defineStore } from 'pinia'
const useStore = defineStore('store', {
state: () => ({
someData: '你好 Pinia',
}),
})
调用 $hydrate
方法:
const store = useStore()
store.$hydrate({ runHooks: false })
这将从 storage
中获取数据并用它替换当前的 state
。并且在上面的示例中,配置了runHooks: false
,所以 beforeRestore
和 afterRestore
钩子函数不会被触发。
强制持久化
除了通过persist
方式设置持久化,每个store
都有$persist
方法来手动触发持久化,这会强制将 store state
保存在已配置的 storage
中。
import { defineStore } from 'pinia'
const useStore = defineStore('store', {
state: () => ({
someData: '你好 Pinia',
}),
})
// App.vue
const store = useStore()
store.$persist()