Vue3组合式API彻底改变了前端状态管理的方式。Pinia作为Vuex的继任者,与组合式API天然契合,提供了更简洁的Store定义和更好的TypeScript支持。在实际项目中,状态持久化、跨Store组合和响应式性能优化是开发者最常遇到的问题。本文围绕Pinia的核心用法,给出持久化存储、Store组合和性能调优的完整方案。
Pinia Store定义与组合式API写法
Pinia支持两种Store定义风格:选项式(Options API)和组合式(Setup Store)。组合式写法使用defineStore配合setup函数,内部直接使用ref、computed和function,与组件内的setup写法完全一致,学习成本为零:
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// state
const token = ref('')
const userInfo = ref(null)
// getters
const isLoggedIn = computed(() => !!token.value)
const userRole = computed(() => userInfo.value?.role ?? 'guest')
// actions
async function login(username: string, password: string) {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
const data = await res.json()
token.value = data.token
userInfo.value = data.user
}
function logout() {
token.value = ''
userInfo.value = null
}
return { token, userInfo, isLoggedIn, userRole, login, logout }
})
组合式Store的优势在于可以自由使用任何composable函数,状态和逻辑的组织方式更灵活。返回的对象属性自动被Pinia包装为响应式,无需额外标注。
持久化存储方案与存储策略
用户登录状态、主题偏好等数据需要持久化到浏览器本地存储。pinia-plugin-persistedstate是官方推荐的持久化插件,支持localStorage和sessionStorage两种引擎,粒度可精确到单个字段:
// main.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
// stores/user.ts - 部分字段持久化
export const useUserStore = defineStore('user', () => {
const token = ref('')
const userInfo = ref(null)
const tempCache = ref({}) // 不持久化的临时数据
return { token, userInfo, tempCache }
}, {
persist: {
key: 'yunthe-user', // 自定义存储key
storage: localStorage, // 使用localStorage
pick: ['token', 'userInfo'], // 仅持久化指定字段
}
})
敏感数据(如token)持久化到localStorage存在XSS风险。更安全的方案是将token存入Cookie并设置HttpOnly标志,通过后端接口读写,Pinia中只保存非敏感的用户信息。持久化策略按数据安全级别划分:公开偏好数据存localStorage,认证凭据存HttpOnly Cookie,会话级临时数据存sessionStorage。
自定义序列化也是常见需求。默认使用JSON.parse/stringify,遇到Date对象或Map时会丢失类型信息。自定义序列化器可以保留类型:
persist: {
key: 'app-settings',
storage: localStorage,
pick: ['theme', 'locale', 'lastVisitTime'],
serializer: {
deserialize: (value: string) => {
const raw = JSON.parse(value)
if (raw.lastVisitTime) {
raw.lastVisitTime = new Date(raw.lastVisitTime)
}
return raw
},
serialize: (value) => JSON.stringify(value)
}
}
跨Store组合与依赖注入
实际项目中Store之间经常存在依赖关系。用户Store中的角色信息影响权限Store的权限计算,权限Store又决定菜单Store的菜单过滤结果。Pinia允许在一个Store中直接调用另一个Store,实现跨Store组合:
// stores/permission.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const usePermissionStore = defineStore('permission', () => {
const userStore = useUserStore()
const routes = ref([])
// 依赖用户Store的角色动态计算权限路由
const allowedRoutes = computed(() => {
return routes.value.filter(route => {
const requiredRole = route.meta?.role
if (!requiredRole) return true
return requiredRole.includes(userStore.userRole)
})
})
async function fetchRoutes() {
const res = await fetch('/api/routes', {
headers: { Authorization: `Bearer ${userStore.token}` }
})
routes.value = await res.json()
}
return { routes, allowedRoutes, fetchRoutes }
})
跨Store引用的顺序很重要:被引用的Store必须先注册。Pinia的Store是惰性创建的,首次调用useXxxStore()时才实例化。在插件中使用Store时需确保Pinia实例已挂载,否则会报错。
响应式性能优化与浅响应
大型对象作为Store状态时,深度响应式代理的性能开销不可忽略。Pinia的state默认经过reactive()包装,对象层级越深、属性越多,代理开销越大。对于不参与模板渲染的大型数据(如表格数据源、树形结构),使用shallowRef替代ref可以跳过深度代理:
export const useTableStore = defineStore('table', () => {
// 浅响应:只追踪.value引用变化,不深度代理内部对象
const tableData = shallowRef([])
const totalCount = ref(0)
const loading = ref(false)
async function fetchData(params: QueryParams) {
loading.value = true
const res = await fetch('/api/data', {
method: 'POST',
body: JSON.stringify(params)
})
const data = await res.json()
// 整体替换触发响应式更新
tableData.value = data.list
totalCount.value = data.total
loading.value = false
}
// 更新单行数据时需要重新赋值整个数组触发更新
function updateRow(index: number, row: Record<string, unknown>) {
const newData = [...tableData.value]
newData[index] = { ...newData[index], ...row }
tableData.value = newData
}
return { tableData, totalCount, loading, fetchData, updateRow }
})
shallowRef的代价是内部属性变化不触发更新,修改行数据后必须整体替换数组或使用triggerRef手动触发。取舍标准:数据只在整块替换时用shallowRef,需要细粒度更新时用ref。表格分页加载场景下,每次翻页都是整块替换,shallowRef优势明显。
Store热更新与开发调试
开发环境中修改Store定义后,默认会触发页面刷新,丢失所有运行时状态。Pinia支持HMR(热模块替换),修改Store代码后保持现有状态不变:
// vite.config.ts 中已自动支持
// 手动配置HMR(仅在非Vite环境下需要)
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
}
Pinia Devtools插件提供时间旅行调试能力,可回溯每次state变更的快照。生产环境中建议关闭Devtools和详细日志,避免性能损耗和敏感信息泄露。
Pinia状态管理方案的核心是组合式API写法+持久化插件+shallowRef优化三者的配合。组合式写法让Store定义与组件代码风格统一,持久化插件解决跨刷新状态丢失问题,shallowRef在大型数据场景下提供必要的性能保障。根据数据安全级别选择合适的存储引擎,根据更新粒度选择响应式深度,即可构建出高效稳定的前端状态管理体系。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-zhuang-tai-guan-li-yu-pinia-chi-jiu-hua/