1. Pinia 基础概念回顾
在深入分析 vue-element-plus-admin 项目的状态管理之前,让我们简要回顾一下 Pinia 的基本概念和优势。
1.1 Pinia 简介
Pinia 是 Vue 生态系统中的新一代状态管理工具,被定位为 Vuex 的继任者,是 Vue 官方推荐的状态管理库。相比 Vuex,Pinia 提供了更简洁的 API、更完善的 TypeScript 支持和更灵活的使用方式。
Pinia 的核心特点:
- 简单直观的 API:不再有 mutations,只有 state、actions 和 getters
- 完整的 TypeScript 支持:自动推断类型,提供更好的开发体验
- 更好的代码分割:按需导入 store,优化应用加载性能
- 组合式 API 风格:与 Vue 3 的组合式 API 完美融合
- 支持多个 Store:模块化设计,无需繁琐的命名空间
- 支持插件系统:可扩展性强,支持状态持久化等功能
1.2 基础用法示例
一个基本的 Pinia store 定义通常包括 state、getters 和 actions 三部分:
typescript
import { defineStore } from 'pinia'
// defineStore 接收两个参数:store ID 和 store 选项
export const useCounterStore = defineStore('counter', {
// state 是 store 的数据(状态)
state: () => ({
count: 0
}),
// getters 类似于 Vue 的计算属性,用于派生状态
getters: {
doubleCount: (state) => state.count * 2
},
// actions 包含可以修改状态、执行异步操作的方法
actions: {
increment() {
this.count++
},
async fetchAndAdd() {
const result = await api.getNumber()
this.count += result
}
}
})
在组件中使用:
vue
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// 可直接访问 state
console.log(store.count)
// 可直接调用 actions
const handleClick = () => store.increment()
// 可使用解构,但会失去响应性
const { count } = store
</script>
2. vue-element-plus-admin 中的状态管理架构
vue-element-plus-admin 项目采用 Pinia 进行状态管理,通过模块化设计使得状态易于组织和维护。
2.1 Store 的初始化与配置
项目在 src/store/index.ts
文件中初始化 Pinia 并配置了持久化插件:
typescript
import type { App } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const store = createPinia()
// 注册持久化插件
store.use(piniaPluginPersistedstate)
export const setupStore = (app: App<Element>) => {
app.use(store)
}
export { store }
这个初始化过程完成了两件重要的事情:
- 创建 Pinia 实例
- 注册持久化插件,使部分状态能够在页面刷新后仍然保持
2.2 模块化组织结构
项目中的 store 按功能模块进行组织,位于 src/store/modules
目录下,主要包含以下几个模块:
- app.ts: 应用全局配置,如布局、主题等
- user.ts: 用户信息和认证相关状态
- permission.ts: 权限和路由相关状态
- tagsView.ts: 标签页视图状态
- locale.ts: 国际化语言设置
- lock.ts: 屏幕锁定功能状态
每个模块专注于特定的功能领域,遵循单一职责原则,使得状态管理更加清晰和可维护。
3. 核心状态模块剖析
下面我们深入分析项目中几个关键的状态模块,以了解其设计思路和实现方式。
3.1 用户状态模块 (user.ts)
用户状态模块负责管理用户的认证信息、个人资料和权限数据:
typescript
export const useUserStore = defineStore('user', {
state: (): UserState => {
return {
userInfo: undefined,
tokenKey: 'Authorization',
token: '',
roleRouters: undefined,
rememberMe: true,
loginInfo: undefined
}
},
getters: {
getTokenKey(): string {
return this.tokenKey
},
// 其他 getters...
},
actions: {
setToken(token: string) {
this.token = token
},
logoutConfirm() {
// 登出确认逻辑...
},
reset() {
// 重置用户状态...
},
// 其他 actions...
},
persist: true // 开启持久化
})
关键设计点:
- 完整的用户状态管理:包含令牌、用户信息和角色路由等信息
- 退出登录功能:提供了确认退出和重置状态的方法
- 全局持久化 :通过
persist: true
启用整个 store 的持久化,使用户登录状态在刷新后保持
3.2 权限状态模块 (permission.ts)
权限模块管理应用的路由和菜单权限,是整个权限系统的核心:
typescript
export const usePermissionStore = defineStore('permission', {
state: (): PermissionState => ({
routers: [],
addRouters: [],
isAddRouters: false,
menuTabRouters: []
}),
getters: {
getRouters(): AppRouteRecordRaw[] {
return this.routers
},
getAddRouters(): AppRouteRecordRaw[] {
return flatMultiLevelRoutes(cloneDeep(this.addRouters))
},
// 其他 getters...
},
actions: {
generateRoutes(
type: 'server' | 'frontEnd' | 'static',
routers?: AppCustomRouteRecordRaw[] | string[]
): Promise<unknown> {
// 根据不同模式生成路由...
},
// 其他 actions...
},
persist: [
{
pick: ['routers'],
storage: localStorage
},
{
pick: ['addRouters'],
storage: localStorage
},
{
pick: ['menuTabRouters'],
storage: localStorage
}
]
})
关键设计点:
- 多种路由生成模式:支持前端、后端和静态三种路由生成方式
- 细粒度持久化 :使用
pick
选择性地持久化特定状态 - 指定存储方式 :明确使用
localStorage
作为持久化存储
3.3 应用配置模块 (app.ts)
app 模块管理应用的全局配置和UI相关设置:
typescript
export const useAppStore = defineStore('app', {
state: (): AppState => {
return {
sizeMap: ['default', 'large', 'small'],
mobile: false,
title: import.meta.env.VITE_APP_TITLE,
pageLoading: false,
breadcrumb: true,
// 其他配置...
theme: {
elColorPrimary: '#409eff',
leftMenuBgColor: '#001529',
// 其他主题配置...
}
}
},
getters: {
// 各种配置的 getters...
},
actions: {
// 设置配置的 actions...
setTheme(theme: ThemeTypes) {
this.theme = Object.assign(this.theme, theme)
},
setCssVarTheme() {
// 设置 CSS 变量主题...
},
// 其他主题相关 actions...
}
})
关键设计点:
- 丰富的应用配置:包含布局、主题、UI元素可见性等多种配置
- 响应式主题系统:通过 actions 提供了动态修改主题的能力
- 环境变量集成:使用环境变量设置应用标题等信息
3.4 标签视图模块 (tagsView.ts)
tagsView 模块管理多标签页导航的状态:
typescript
export const useTagsViewStore = defineStore('tagsView', {
state: (): TagsViewState => ({
visitedViews: [],
cachedViews: new Set(),
selectedTag: undefined
}),
getters: {
// ...
},
actions: {
addView(view: RouteLocationNormalizedLoaded): void {
this.addVisitedView(view)
this.addCachedView()
},
delView(view: RouteLocationNormalizedLoaded) {
this.delVisitedView(view)
this.addCachedView()
},
// 其他标签页操作...
}
})
关键设计点:
- 访问历史与缓存分离:分别管理 visitedViews(显示的标签)和 cachedViews(组件缓存)
- 丰富的标签操作:支持添加、删除、清空、左右删除等多种标签操作
- 组件缓存优化:使用 Set 数据结构高效管理缓存组件
4. 持久化存储方案
vue-element-plus-admin 项目采用 pinia-plugin-persistedstate
插件实现状态持久化,并提供了多种灵活的持久化配置方式。
4.1 全局持久化配置
在 src/store/index.ts
中,项目注册了持久化插件:
typescript
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(piniaPluginPersistedstate)
这使得所有启用了持久化的 store 模块都能将状态保存到浏览器存储中。
4.2 模块级持久化配置
项目中不同的 store 模块采用了不同的持久化策略:
-
简单持久化:如用户模块和锁屏模块
typescriptexport const useUserStore = defineStore('user', { // state, getters, actions... persist: true // 开启整个 store 的持久化 })
-
选择性持久化:如权限模块
typescriptexport const usePermissionStore = defineStore('permission', { // state, getters, actions... persist: [ { pick: ['routers'], // 只持久化 routers 属性 storage: localStorage // 指定使用 localStorage }, { pick: ['addRouters'], storage: localStorage }, { pick: ['menuTabRouters'], storage: localStorage } ] })
4.3 自定义存储工具
项目还封装了 useStorage
hook,提供了更灵活的存储管理:
typescript
export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionStorage') => {
const setStorage = (key: string, value: any) => {
const valueType = getValueType(value)
window[type].setItem(key, JSON.stringify({ type: valueType, value }))
}
const getStorage = (key: string) => {
// 读取并解析存储的值...
}
const removeStorage = (key: string) => {
window[type].removeItem(key)
}
const clear = (excludes?: string[]) => {
// 清除存储,支持排除项...
}
return {
setStorage,
getStorage,
removeStorage,
clear
}
}
这个工具的特点:
- 类型保留:保存数据时记录值的类型,确保还原时类型一致
- 灵活选择存储位置:支持 localStorage 和 sessionStorage
- 部分清除能力:提供带排除项的清除功能
- API 简化:简化存储操作,使用更加方便
5. 复杂状态处理策略
vue-element-plus-admin 项目在处理复杂状态时采用了多种策略,确保状态管理的清晰性和高效性。
5.1 状态初始化与重置
项目中的状态初始化和重置主要通过以下方式实现:
-
状态初始化 :每个 store 在定义时都通过
state()
函数提供初始状态typescriptstate: (): UserState => { return { userInfo: undefined, tokenKey: 'Authorization', token: '', // 其他初始状态... } }
-
状态重置:提供专门的 reset 或 clear 方法
typescriptreset() { const tagsViewStore = useTagsViewStore() tagsViewStore.delAllViews() this.setToken('') this.setUserInfo(undefined) this.setRoleRouters([]) router.replace('/login') }
5.2 状态间的协作
项目中 store 之间的协作主要通过以下方式实现:
-
直接导入使用:在一个 store 中导入并使用另一个 store
typescriptimport { useTagsViewStore } from './tagsView' // 在 actions 中使用 reset() { const tagsViewStore = useTagsViewStore() tagsViewStore.delAllViews() // 其他操作... }
-
WithOut 辅助函数:项目为每个 store 提供了 WithOut 版本的辅助函数,方便在非组件环境中使用
typescriptexport const useUserStoreWithOut = () => { return useUserStore(store) }
这使得在路由守卫等位置可以方便地获取 store 实例:
typescript// 在 router 中使用 const userStore = useUserStoreWithOut() if (userStore.getUserInfo) { // 用户已登录... }
5.3 深度状态更新
对于嵌套的复杂状态,项目采用了深度合并的方法进行更新:
typescript
// 在 app store 中更新主题
setTheme(theme: ThemeTypes) {
this.theme = Object.assign(this.theme, theme)
}
// 对 CSS 变量的处理
setCssVarTheme() {
for (const key in this.theme) {
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
}
}
这种方式允许部分更新复杂对象,同时保持其他属性不变。
6. 与 Vue 组件的交互模式
vue-element-plus-admin 项目中 Pinia 与 Vue 组件之间的交互展现了多种模式和最佳实践。
6.1 组件中的基础用法
在组件中使用 store 的基本方式:
vue
<script setup lang="ts">
import { useUserStore } from '@/store/modules/user'
// 获取 store 实例
const userStore = useUserStore()
// 访问状态
const username = computed(() => userStore.getUserInfo?.username)
// 调用 actions
const handleLogout = () => {
userStore.logoutConfirm()
}
</script>
6.2 自定义 Hooks 封装
项目大量使用自定义 hooks 封装 store 操作,提高代码复用性:
typescript
// src/hooks/web/useLocale.ts
export const useLocale = () => {
const { t, locale } = useI18n()
const localeStore = useLocaleStoreWithOut()
const setLocale = async (lang: string) => {
await localeStore.setCurrentLocale({
lang,
elLocale: elLocaleMap[lang]
})
locale.value = lang
}
return {
t,
setLocale
}
}
组件中使用这些 hooks:
vue
<script setup>
import { useLocale } from '@/hooks/web/useLocale'
const { t, setLocale } = useLocale()
const changeLang = (lang) => {
setLocale(lang)
}
</script>
6.3 状态驱动的 UI 组件
项目中许多 UI 组件直接从 store 读取状态,实现响应式更新:
vue
<!-- 根据全局主题配置调整样式 -->
<template>
<div
class="left-menu"
:style="{
backgroundColor: getBgColor,
borderRightColor: getBorderColor
}"
>
<!-- 菜单内容 -->
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
const getBgColor = computed(() => appStore.getTheme.leftMenuBgColor)
const getBorderColor = computed(() => appStore.getTheme.leftMenuBorderColor)
</script>
6.4 状态变化的监听与响应
项目中常见的状态变化监听模式:
typescript
// 监听主题变化
watch(
() => appStore.getIsDark,
(val) => {
if (val) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
},
{ immediate: true }
)
7. 状态管理最佳实践
通过分析 vue-element-plus-admin 项目的状态管理实现,我们可以总结出以下最佳实践:
7.1 模块化设计
- 按功能划分 store:每个 store 模块专注于特定功能领域
- 单一职责原则:store 模块应该只负责一个功能领域的状态管理
- 合理的粒度:既不过于细碎,也不过于庞大
7.2 类型安全
-
为所有状态定义接口:
typescriptinterface UserState { userInfo?: UserType tokenKey: string token: string // 其他属性... }
-
为 getters 和 actions 提供返回类型:
typescriptgetters: { getToken(): string { return this.token } }
-
使用 TypeScript 泛型:
typescriptconst findIndex = <T>(list: T[], predicate: (item: T) => boolean): number => { // 实现... }
7.3 状态访问控制
- 通过 getters 访问状态:避免直接访问 state,提供 getters 作为公共 API
- 通过 actions 修改状态:所有状态修改都通过 actions 进行,确保操作的一致性
- WithOut 模式:提供在任何地方都可访问的 store 实例获取方法
7.4 持久化策略
- 选择性持久化:只持久化必要的状态,避免存储过多数据
- 存储位置选择:根据数据重要性和生命周期选择 localStorage 或 sessionStorage
- 敏感数据处理:避免持久化存储敏感信息
7.5 性能优化
- 按需导入 store:只在需要的组件中导入 store,减少不必要的依赖
- 避免过度响应:合理设计状态结构,避免不必要的组件重渲染
- 缓存计算结果:使用计算属性和 getters 缓存派生状态
8. 总结
vue-element-plus-admin 项目的状态管理实现充分展现了 Pinia 的强大功能和灵活性。通过模块化设计、TypeScript 类型支持、状态持久化、自定义 hooks 封装等技术手段,项目实现了清晰、高效、可维护的状态管理系统。
这种状态管理方案不仅为应用提供了可靠的数据流控制,也为开发者提供了良好的开发体验和代码组织方式。它代表了 Vue 3 生态下状态管理的最佳实践,值得在实际项目中借鉴和应用。
在下一期中,我们将深入探讨 vue-element-plus-admin 项目的路由系统设计和权限控制实现,进一步了解这个企业级前端项目的核心架构。