pinia是Vue的存储库,它允许您跨组件/页面共享状态。它Vuex的替代品,其功能和性能更优于vuex,在后期使用中会逐渐被大家发现。
这里我们先使用pinia全局存储用户信息、接口访问令牌、菜单列表等数据,当然这些只是作为一个项目的基础部分,其他共享信息可根据你们的项目需求进行添加。
一、安装
在创建项目时,一般会有选项可以选择是否安装Pinia,选择是即可。如果创建项目里错过了也没关系,通过命令安装即可。代码如下:
// npm安装
npm i pinia
// yarn安装
yarn add pinia
// pnpm 安装
pnpm add pinia
二、Pinia使用
2.1 定义数据类型
在Vue3+ts开中,对变量的定义和书写规则,则需要按ts语法进行编写,否则编辑器则会显示波浪线并提示您。
在项目的src/types目录下创建global.ts,用于存储类型数据。代码如下:
// 用户数据 接口
export interface UserInfo {
id?: number // id
username: string // 用户名
avatar?: string // 头像
}
export type UserInfoType = UserInfo
// 菜单信息 接口
export interface MenuListInfo {
id: number
name: string,
icon?: string,
children: Array<MenuListInfo>
}
export type MenuListInfoType = MenuListInfo
2.2 创建global.ts
用户信息、访问令牌、菜单令牌在项目中使用频率较高,这里将这些信息放在global.js全局文件中。在src/stores目录中创建global.js,代码如下:
import { ref, computed, reactive } from 'vue'
import { defineStore } from 'pinia'
import type { UserInfoType, MenuListInfoType } from '@/types/global'
// 用户信息
export const useUserInfoStore = defineStore('userInfo', () => {
// 定义用户信息状态(添加默认数据)
let userInfo = reactive<UserInfoType>({username: '匿名'})
// 定义getters获取用户信息响应数据
const user_info = computed(() => userInfo)
// 定义actions添加或修改用户信息函数
const userInfoChange = (_value: UserInfoType) => userInfo = _value
return { user_info, userInfoChange }
})
// 访问令牌
export const useAccessTokenStore = defineStore('accessToken', () => {
// 定义访问令牌状态
const accessToken = ref<string>('')
// 定义getters获取访问令牌响应式数据
const access_token = computed(() => accessToken)
// 定义actions添加或修改访问令牌
const accessTokenChange = (_value: string) => accessToken.value = _value
return { access_token, accessTokenChange }
})
// 菜单列表令牌
export const useMenuListStore = defineStore('menuList', () => {
// 定义菜单列表状态
let menuList = reactive<Array<MenuListInfoType>>([])
// 定义getters获取菜单数据
const menu_list = computed(() => menuList)
// 定义actions添加或修改菜单信息
const menuListChange = (_value: Array<MenuListInfoType>) => menuList = _value
return { menu_list, menuListChange }
})
2.3 创建login.ts
当登录成功后,一般接口会同时返回用户信息和访问令牌,如果我们不希望引入useUserInfoStore后又引入useAccessTokenStore,使用一个useStore即可完成存储操作;此时可以在src/stores目录下再创建一个login.ts,专门用于处理登录相关数据。代码如下:
import { defineStore } from 'pinia'
import type { UserInfoType } from '@/types/global'
import { useUserInfoStore, useAccessTokenStore } from './global'
// 登录统一处理用户信息和访问令牌
export const useLoginStore = defineStore('login', () => {
const { userInfoChange } = useUserInfoStore()
const { accessTokenChange } = useAccessTokenStore()
// 定义actions修改用户信息和访问令牌令牌
const loginChange = (_userInfo: UserInfoType, _token: string) => {
userInfoChange(_userInfo)
accessTokenChange(_token)
}
return { loginChange }
})
三、pinia持久化
在Vue2+vuex中,如果需要对数据持久化,则可使用vue-ls等插件,在actions的函数执行时,通过Vue.ls.set('名称', '值')将数据缓存到本地;而对于Pinia持久化操作,则更为方便,安装pinia-plugin-persistedstate插件后,只需要在defineStore配置下,即可自动将状态数据缓存到本地。
第一步:安装pinia-plugin-persistedstate(注:这里安装的是@4.0.0版本),代码如下:
pnpm i pinia-plugin-persistedstate
第二步:main.js安装插件,代码如下:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
import App from './App.vue'
import router from './router'
const app = createApp(App)
// 安装pinia-plugin-persistedstate
app.use(createPinia().use(persist))
app.use(router)
app.mount('#app')
第三步:在defineStore的第三个参数上,添加persist: true,代码如下:
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import type { UserInfoType, MenuListInfoType } from '@/types/global'
// 用户信息
export const useUserInfoStore = defineStore('userInfo', () => {
// 定义用户信息状态(添加默认数据)
const userInfo = ref<UserInfoType>({username: '匿名'})
// 定义getters获取用户信息响应数据
const user_info = computed(() => userInfo)
// 定义actions添加或修改用户信息函数
const userInfoChange = (_value: UserInfoType) => userInfo.value = _value
return { userInfo, user_info, userInfoChange }
}, {
persist: true
})
// 访问令牌
export const useAccessTokenStore = defineStore('accessToken', () => {
// 定义访问令牌状态
const accessToken = ref<string>('')
// 定义getters获取访问令牌响应式数据
const access_token = computed(() => accessToken)
// 定义actions添加或修改访问令牌
const accessTokenChange = (_value: string) => accessToken.value = _value
return { accessToken, access_token, accessTokenChange }
}, {
persist: true
})
// 菜单列表令牌
export const useMenuListStore = defineStore('menuList', () => {
// 定义菜单列表状态
const menuList = ref<Array<MenuListInfoType>>([])
// 定义getters获取菜单数据
const menu_list = computed(() => menuList)
// 定义actions添加或修改菜单信息
const menuListChange = (_value: Array<MenuListInfoType>) => menuList.value = _value
return { menuList, menu_list, menuListChange }
}, {
persist: true
})
此时在页面中调用useLoginStore ,数据将会自动缓存到本地存储中,调用代码如下:
<script setup lang="ts">
import { useLoginStore } from '@/stores/login'
const login = useLoginStore()
// 演示延迟响应数据
setTimeout(() => {
login.loginChange({username: 'tome'}, "test1234")
}, 2000);
</script>
如下图:
注意:细心朋友可能已发现了,在pinia持久化第三步中,代码已悄然有所改变。这是因为必须要作相应调整,否则就算配置了persist: true,也无法缓存状态数据。需要注意以下几点:
- 定义状态数据时,不要使用reactive,改为ref来定义,这里因pinia-plugin-persistedstate无法缓存reactive响应数据。
- 另外必须要注意的是,如果你希望缓存userInfo、accessToken这些状态数据,必须在return中返回这些状态变量,否则无法缓存。
- 当stores中某个defineStore中配置persist:后,如未生效,可重启服务后查看!
对于persist来说,还有更多参数可供设置,如下表:
序号 | 名称 | 类型 | 描述 |
---|---|---|---|
1 | key | type: string default: store.$id | 用于引用存储中存储的反序列化数据。 |
2 | storage | type: StorageLike default: localStorage | 将数据持久化到的存储。必须有getItem: (key: string) => string | null和setItem: (key: string, value: string) => void方法。 |
3 | serializer | type: Serializer default: JSON.stringify / destr | 自定义序列化器在持久化之前序列化数据,并在重新填充存储之前反序列化数据。必须有serialize: (value: StateTree) => string和deserialize: (value: string) => StateTree方法。 |
4 | pick | type: string[] | Path<StateTree>[] default: undefined | 点表示法路径数组,用于选择应该持久化的内容。[]表示不保留状态,undefined表示保留整个状态。 |
5 | omit | type: string[] | Path<StateTree>[] default: undefined | 要从应该持久化的内容中省略的点表示法路径数组。[]或undefined表示整个状态保持不变(没有遗漏)。 |
6 | beforeHydrate | type: (context: PiniaPluginContext) => void default: undefined | 钩子函数在用持久化数据填充存储状态之前运行。这个钩子可以访问整个PiniaPluginContext。这可以用来在缓存之前强制执行特定的动作。 |
7 | afterHydrate | type: (context: PiniaPluginContext) => void default: undefined | 在重新激活持久状态后运行钩子函数。这个钩子可以访问整个PiniaPluginContext。这可以用来加强缓存后的具体行动。 |
8 | debug | type: boolean default: false | 当设置为true时,在持久化/水化存储时可能发生的任何错误都将使用console.error记录。 |
3.1 key用法
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
state: () => ({
someState: 'hello pinia',
}),
persist: {
key: 'my-custom-key',
},
})
3.2 storage用法
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
state: () => ({
someState: 'hello pinia',
}),
persist: {
storage: sessionStorage,
},
})
3.3 serializer用法
import { defineStore } from 'pinia'
import { parse, stringify } from 'zipson'
export const useStore = defineStore('store', {
state: () => ({
someState: 'hello pinia',
}),
persist: {
serializer: {
deserialize: parse,
serialize: stringify
}
},
})
3.4 pick用法
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
state: () => ({
save: {
me: 'saved',
notMe: 'not-saved',
},
saveMeToo: 'saved',
}),
persist: {
pick: ['save.me', 'saveMeToo'],
},
})
3.5 omit用法
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
state: () => ({
ignore: {
me: 'not-saved',
notMe: 'saved',
},
ignoreMeToo: 'not-saved',
}),
persist: {
omit: ['ignore.me', 'ignoreMeToo'],
},
})
3.6 beforeHydrate用法
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
state: () => ({
someState: 'hello pinia',
}),
persist: {
beforeHydrate: (ctx) => {
console.log(`about to hydrate '${ctx.store.$id}'`)
}
},
})
3.7 afterHydrate用法
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
state: () => ({
someState: 'hello pinia',
}),
persist: {
afterHydrate: (ctx) => {
console.log(`just hydrated '${ctx.store.$id}'`)
}
},
})
另外,需要了解更多相关信息,可去官网查看:Configuration | Pinia Plugin Persistedstate
四、key添加前缀
在项目开发中,经常会看到很多本地缓存,都带有统一的前缀,这是怎么做到的呢?从目前来看,如果自定义缓存key值,可以在开始persist位置自定义key,但是否可以统一修改呢?当然也是可以的,代码如下:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
pinia.use(createPersistedState({
debug: true,
key: (value) => "Test_" + value
}))
app.use(pinia)
app.mount('#app')
重启服务后,再看缓存数据,key值则统一被添加了前缀。如下图:
六、Pinia的改造
示例代码:
<script setup lang="ts">
import { useLoginStore } from '@/stores/login'
const login = useLoginStore()
// 演示延迟响应数据
setTimeout(() => {
login.loginChange({username: 'tome'}, "test1234")
}, 2000);
</script>
如代码可见,后期如果useStore定义越来越多,引用地址会更为复杂,如何统一引入同一个地址,就可以读取到所有useStore呢?这也是可以实现的,首先将目录结构调整如下图:
后期所有子模块store可以定义在modules目录中,并且stores目录下所有useStore通过stores/index.ts统一导出。
首先,将main.ts中定义的pinia部分,移到stores/index.ts中,代码如下:
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(createPersistedState({
debug: true,
key: (value) => "Test_" + value
}))
export default pinia
// 使用通配符将引入地址(modules/login)中非default的全部导出
export * from './global'
export * from './modules/login'
然后在main.ts中引入@/stores,将pinia安装到应用到,代码如下:
import { createApp } from 'vue'
import pinia from '@/stores'
import App from './App.vue'
const app = createApp(App)
app.use(pinia)
app.mount('#app')
此时,我们就可以直接通过@/store/index引入,直接读取到useLoginStore 了,后期不管定义多少useStore,都可以通过该统一地址获取。代码如下:
<script setup lang="ts">
import { useLoginStore } from '@/stores/index'
const { loginChange } = useLoginStore()
setTimeout(() => {
loginChange({username: 'tome'}, "test1234")
}, 2000);
</script>
到这里,Pinia的安装、使用、持久化、key添加前缀、统一输出等已全部讲完,希望对大家有所帮助。