Vue3入门 - Pinia使用(替代Vuex)

        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,也无法缓存状态数据。需要注意以下几点:

  1. 定义状态数据时,不要使用reactive,改为ref来定义,这里因pinia-plugin-persistedstate无法缓存reactive响应数据。
  2. 另外必须要注意的是,如果你希望缓存userInfo、accessToken这些状态数据,必须在return中返回这些状态变量,否则无法缓存。
  3. 当stores中某个defineStore中配置persist:后,如未生效,可重启服务后查看!

        对于persist来说,还有更多参数可供设置,如下表:

序号名称类型描述
1key

type: string

default: store.$id

用于引用存储中存储的反序列化数据。
2storage

type: StorageLike

default: localStorage

将数据持久化到的存储。必须有getItem: (key: string) => string | null和setItem: (key: string, value: string) => void方法。
3serializer

type: Serializer

default: JSON.stringify / destr

自定义序列化器在持久化之前序列化数据,并在重新填充存储之前反序列化数据。必须有serialize: (value: StateTree) => string和deserialize: (value: string) => StateTree方法。
4pick

type: string[] | Path<StateTree>[]

default: undefined

点表示法路径数组,用于选择应该持久化的内容。[]表示不保留状态,undefined表示保留整个状态。
5omit

type: string[] | Path<StateTree>[]

default: undefined

要从应该持久化的内容中省略的点表示法路径数组。[]或undefined表示整个状态保持不变(没有遗漏)。
6beforeHydratetype: (context: PiniaPluginContext) => void
default: undefined
钩子函数在用持久化数据填充存储状态之前运行。这个钩子可以访问整个PiniaPluginContext。这可以用来在缓存之前强制执行特定的动作。
7afterHydratetype: (context: PiniaPluginContext) => void
default: undefined
在重新激活持久状态后运行钩子函数。这个钩子可以访问整个PiniaPluginContext。这可以用来加强缓存后的具体行动。
8debugtype: 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添加前缀、统一输出等已全部讲完,希望对大家有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值