引言:为什么UniApp需要现代化的状态管理?
随着UniApp生态的成熟和Vue3的普及,传统的Vuex状态管理方案在复杂跨端应用中逐渐暴露出诸多问题:
- TypeScript支持不够完善
- 模块化组织复杂
- 组合式API集成不够优雅
- 开发体验和调试体验有待提升
Pinia作为Vue官方推荐的状态管理库,完美解决了这些问题。本文将深入探讨如何在UniApp项目中集成Pinia,构建可维护、高性能的企业级应用架构。
第一部分:UniApp + Vue3 + Pinia 环境搭建
1.1 创建UniApp项目
# 使用Vue3模板创建UniApp项目
npx degit dcloudio/uni-preset-vue#vite my-uniapp-project
# 进入项目目录
cd my-uniapp-project
# 安装依赖
npm install
# 安装Pinia及相关依赖
npm install pinia @pinia/native-plugin
1.2 项目结构配置
my-uniapp-project/
├── src/
│ ├── stores/ # Pinia状态管理
│ │ ├── modules/ # 业务模块store
│ │ ├── index.ts # store入口文件
│ │ └── types/ # 类型定义
│ ├── composables/ # 组合式函数
│ ├── pages/ # 页面文件
│ ├── static/ # 静态资源
│ └── main.ts # 应用入口
├── uni.scss # 全局样式
├── manifest.json # 应用配置
└── package.json
1.3 初始化Pinia配置
// src/stores/index.ts
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
// 创建Pinia实例
const pinia = createPinia()
// 添加持久化插件(针对不同平台的适配)
pinia.use(createPersistedState({
storage: {
getItem(key: string): string | null {
// 跨端存储适配
#ifdef H5
return localStorage.getItem(key)
#endif
#ifdef MP-WEIXIN
return uni.getStorageSync(key)
#endif
#ifdef APP-PLUS
return plus.storage.getItem(key)
#endif
},
setItem(key: string, value: string) {
#ifdef H5
localStorage.setItem(key, value)
#endif
#ifdef MP-WEIXIN
uni.setStorageSync(key, value)
#endif
#ifdef APP-PLUS
plus.storage.setItem(key, value)
#endif
}
}
}))
export default pinia
第二部分:Pinia核心概念与基础使用
2.1 创建第一个Store
// src/stores/modules/user.ts
import { defineStore } from 'pinia'
import type { UserInfo, LoginParams } from '../types/user'
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
userInfo: null as UserInfo | null,
isLoggedIn: false,
loginLoading: false
}),
getters: {
// 计算属性
userName: (state) => state.userInfo?.name || '未登录',
userId: (state) => state.userInfo?.id || '',
// 带参数的计算属性
hasPermission: (state) => (permission: string) => {
return state.userInfo?.permissions?.includes(permission) || false
}
},
actions: {
// 异步action
async login(params: LoginParams) {
this.loginLoading = true
try {
const response = await uni.request({
url: '/api/user/login',
method: 'POST',
data: params
})
this.token = response.data.token
this.userInfo = response.data.userInfo
this.isLoggedIn = true
// 登录成功后跳转
uni.switchTab({
url: '/pages/home/index'
})
} catch (error) {
uni.showToast({
title: '登录失败',
icon: 'error'
})
throw error
} finally {
this.loginLoading = false
}
},
// 同步action
logout() {
this.$reset() // 重置state
uni.removeStorageSync('token')
uni.reLaunch({
url: '/pages/login/index'
})
},
// 更新用户信息
updateUserInfo(info: Partial) {
if (this.userInfo) {
this.userInfo = { ...this.userInfo, ...info }
}
}
},
// 持久化配置
persist: {
key: 'user-store',
paths: ['token', 'userInfo', 'isLoggedIn']
}
})
2.2 在组件中使用Store
<template>
<view class="user-profile">
<view v-if="userStore.isLoggedIn">
<image :src="userStore.userInfo?.avatar" class="avatar" />
<text class="name">{{ userStore.userName }}</text>
<text class="welcome">欢迎回来!</text>
<button @click="handleLogout" :loading="userStore.loginLoading">
退出登录
</button>
</view>
<view v-else>
<button @click="goToLogin">立即登录</button>
</view>
</view>
</template>
<script setup lang="ts">
import { useUserStore } from '@/stores/modules/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
// 使用storeToRefs保持响应性
const { userName, isLoggedIn } = storeToRefs(userStore)
const handleLogout = () => {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
userStore.logout()
}
}
})
}
const goToLogin = () => {
uni.navigateTo({
url: '/pages/login/index'
})
}
</script>
第三部分:高级状态管理技巧
3.1 模块化Store组织
// src/stores/modules/cart.ts - 购物车模块
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as CartItem[],
selectedIds: [] as string[],
totalPrice: 0
}),
getters: {
itemCount: (state) => state.items.length,
selectedCount: (state) => state.selectedIds.length,
selectedItems: (state) => {
return state.items.filter(item =>
state.selectedIds.includes(item.id)
)
}
},
actions: {
// 与其他store交互
async checkout() {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
uni.navigateTo({ url: '/pages/login/index' })
return
}
// 结算逻辑
const orderStore = useOrderStore()
await orderStore.createOrder(this.selectedItems)
// 清空已选商品
this.clearSelected()
}
}
})
// src/stores/modules/order.ts - 订单模块
export const useOrderStore = defineStore('order', {
// 订单相关状态管理
})
3.2 组合式Store模式
// src/stores/composables/usePagination.ts
import { ref, computed } from 'vue'
export function usePagination(store: any, fetchAction: string) {
const currentPage = ref(1)
const pageSize = ref(10)
const loading = ref(false)
const total = computed(() => store.total || 0)
const totalPages = computed(() =>
Math.ceil(total.value / pageSize.value)
)
const loadData = async (page = 1) => {
if (loading.value) return
loading.value = true
try {
await store[fetchAction]({
page,
pageSize: pageSize.value
})
currentPage.value = page
} finally {
loading.value = false
}
}
const nextPage = () => {
if (currentPage.value {
if (currentPage.value > 1) {
loadData(currentPage.value - 1)
}
}
return {
currentPage,
pageSize,
loading,
total,
totalPages,
loadData,
nextPage,
prevPage
}
}
// 在Store中使用
export const useProductStore = defineStore('product', {
state: () => ({
products: [],
total: 0
}),
actions: {
async fetchProducts(params: PaginationParams) {
const response = await uni.request({
url: '/api/products',
data: params
})
this.products = response.data.list
this.total = response.data.total
}
}
})
// 在组件中使用
const productStore = useProductStore()
const pagination = usePagination(productStore, 'fetchProducts')
// 初始化加载
onMounted(() => {
pagination.loadData(1)
})
第四部分:性能优化与最佳实践
4.1 状态持久化策略
// src/stores/plugins/storage.ts
import { PiniaPluginContext } from 'pinia'
export function createStoragePlugin() {
return (context: PiniaPluginContext) => {
const { store, options } = context
// 自动清理过期数据
if (options.persist?.expires) {
const savedTime = uni.getStorageSync(`${store.$id}-saved-time`)
if (savedTime && Date.now() - savedTime > options.persist.expires) {
uni.removeStorageSync(`${store.$id}-data`)
uni.removeStorageSync(`${store.$id}-saved-time`)
}
}
// 监听存储变化(多端同步)
#ifdef H5
window.addEventListener('storage', (e) => {
if (e.key === `${store.$id}-data`) {
store.$patch(JSON.parse(e.newValue || '{}'))
}
})
#endif
}
}
// Store配置示例
export const useAuthStore = defineStore('auth', {
persist: {
key: 'auth-store',
paths: ['token', 'userInfo'],
expires: 7 * 24 * 60 * 60 * 1000, // 7天过期
storage: 'local' // 指定存储类型
}
})
4.2 状态更新优化
// 使用批量更新减少渲染次数
export const useListStore = defineStore('list', {
actions: {
// 不推荐的写法:多次更新state
async fetchDataBad() {
this.loading = true
const data = await api.getData()
this.items = data.items // 第一次更新
this.total = data.total // 第二次更新
this.loading = false // 第三次更新
},
// 推荐的写法:批量更新
async fetchDataGood() {
this.loading = true
try {
const data = await api.getData()
// 使用$patch一次性更新
this.$patch({
items: data.items,
total: data.total,
loading: false
})
} catch (error) {
this.$patch({
loading: false,
error: error.message
})
}
},
// 使用action的$patch方法
updateMultipleItems(updates: Record) {
this.$patch((state) => {
Object.keys(updates).forEach(key => {
if (key in state) {
state[key] = updates[key]
}
})
})
}
}
})
第五部分:实战案例 – 电商应用状态管理
5.1 完整的电商Store架构
// src/stores/modules/ecommerce/index.ts
import { useUserStore } from '../user'
import { useCartStore } from '../cart'
import { useProductStore } from '../product'
import { useOrderStore } from '../order'
import { useAddressStore } from '../address'
// 电商业务聚合Store
export const useEcommerceStore = defineStore('ecommerce', () => {
const userStore = useUserStore()
const cartStore = useCartStore()
const productStore = useProductStore()
const orderStore = useOrderStore()
const addressStore = useAddressStore()
// 全局加载状态
const globalLoading = ref(false)
// 初始化电商数据
const initializeEcommerce = async () => {
if (!userStore.isLoggedIn) return
globalLoading.value = true
try {
// 并行加载必要数据
await Promise.all([
cartStore.fetchCart(),
addressStore.fetchAddresses(),
productStore.fetchRecommendations()
])
} finally {
globalLoading.value = false
}
}
// 一键下单
const quickOrder = async (productId: string, skuId: string) => {
if (!userStore.isLoggedIn) {
uni.navigateTo({ url: '/pages/login/index' })
return
}
// 1. 获取商品详情
const product = await productStore.fetchProductDetail(productId)
// 2. 添加到购物车
await cartStore.addItem({
productId,
skuId,
quantity: 1,
price: product.price
})
// 3. 创建订单
const orderId = await orderStore.createFromCart([skuId])
// 4. 跳转到支付
uni.navigateTo({
url: `/pages/order/payment?id=${orderId}`
})
}
// 监听用户登录状态变化
watch(() => userStore.isLoggedIn, (loggedIn) => {
if (loggedIn) {
initializeEcommerce()
} else {
// 用户退出,清理数据
cartStore.clear()
orderStore.clear()
}
})
return {
globalLoading,
initializeEcommerce,
quickOrder,
// 暴露子store的state(按需)
user: userStore,
cart: cartStore,
product: productStore,
order: orderStore,
address: addressStore
}
})
5.2 跨页面状态同步方案
// src/stores/plugins/sync.ts - 跨页面状态同步插件
export function createSyncPlugin() {
return ({ store }: PiniaPluginContext) => {
// 监听store变化
store.$subscribe((mutation, state) => {
// 广播状态变化到其他页面
#ifdef MP-WEIXIN
const pages = getCurrentPages()
pages.forEach(page => {
if (page?.$vm?.$pinia) {
// 更新其他页面的store状态
page.$vm.$pinia.state.value[store.$id] = state
}
})
#endif
#ifdef H5
// 使用Broadcast Channel API
if (typeof BroadcastChannel !== 'undefined') {
const channel = new BroadcastChannel(`pinia-${store.$id}`)
channel.postMessage({
type: 'state-update',
state: JSON.parse(JSON.stringify(state))
})
channel.close()
}
#endif
})
// 接收其他页面的状态更新
#ifdef H5
if (typeof BroadcastChannel !== 'undefined') {
const channel = new BroadcastChannel(`pinia-${store.$id}`)
channel.onmessage = (event) => {
if (event.data.type === 'state-update') {
store.$patch(event.data.state)
}
}
}
#endif
}
}
5.3 错误处理与监控
// src/stores/plugins/errorHandler.ts
export function createErrorHandlerPlugin() {
return ({ store }: PiniaPluginContext) => {
const originalActions = { ...store }
// 包装所有actions
Object.keys(store).forEach(key => {
if (typeof store[key] === 'function') {
const originalAction = store[key]
store[key] = async function(...args: any[]) {
try {
return await originalAction.apply(this, args)
} catch (error) {
// 统一错误处理
console.error(`Store ${store.$id} action ${key} error:`, error)
// 显示错误提示
uni.showToast({
title: '操作失败,请重试',
icon: 'error'
})
// 上报错误
if (process.env.NODE_ENV === 'production') {
// 错误上报逻辑
reportError(error, {
store: store.$id,
action: key,
args
})
}
throw error
}
}
}
})
}
}
// 在main.ts中注册插件
pinia.use(createErrorHandlerPlugin())
总结:构建可维护的UniApp状态管理架构
通过本文的深入探讨,我们构建了一个完整的UniApp + Vue3 + Pinia状态管理解决方案。这套架构具有以下优势:
- 类型安全:完整的TypeScript支持,提供更好的开发体验
- 模块化:清晰的Store组织,便于团队协作
- 高性能:优化的更新策略,减少不必要的渲染
- 跨端兼容:完善的平台适配,一次编写多端运行
- 可维护性:插件化架构,便于扩展和维护
在实际项目中,建议根据业务复杂度选择合适的架构模式。对于中小型应用,可以直接使用基础的Pinia Store;对于大型企业级应用,推荐采用本文介绍的模块化+组合式架构。
记住,良好的状态管理不仅是技术选择,更是工程实践的体现。合理规划Store结构、优化状态更新、完善错误处理,这些都能显著提升应用的质量和开发效率。

