免费资源下载
从零到一构建支持小程序、H5、App的多端状态管理解决方案,解决跨端数据同步核心难题
UniApp 3.0+
Vue3 + Pinia
跨端数据同步
企业级架构
Vue3 + Pinia
跨端数据同步
企业级架构
🚀 为什么UniApp需要专门的状态管理方案?
在UniApp多端开发中,传统Vuex面临诸多挑战:小程序端存储限制、App端持久化差异、H5端状态同步等问题。Pinia作为Vue3官方推荐的状态管理库,结合UniApp特性,能提供更优雅的解决方案。
🔍 多端存储差异对比
- 小程序:Storage上限10MB,异步API
- App:支持原生存储,容量更大
- H5:LocalStorage同步API,5MB限制
- 快应用:自有存储体系
🎯 Pinia核心优势
- TypeScript友好,完整类型推断
- 组合式API,逻辑更清晰
- 模块化设计,按需引入
- Devtools支持,调试方便
💻 实战:构建跨端状态管理架构
第一步:项目初始化与Pinia配置
// 1. 创建UniApp项目(使用Vue3版本)
// vue create -p dcloudio/uni-preset-vue#vue3 project-name
// 2. 安装Pinia及相关依赖
// npm install pinia @pinia/native-unistorage
// 3. 创建store入口文件:/store/index.js
import { createPinia } from 'pinia'
import { createPersistedState } from '@pinia/native-unistorage'
const pinia = createPinia()
// 配置跨端持久化插件
pinia.use(createPersistedState({
storage: {
getItem(key) {
// 统一多端存储API
return new Promise((resolve) => {
uni.getStorage({
key,
success: (res) => resolve(res.data),
fail: () => resolve(null)
})
})
},
setItem(key, value) {
return new Promise((resolve) => {
uni.setStorage({
key,
data: value,
success: resolve
})
})
}
}
}))
export default pinia
// 4. 在main.js中挂载
import { createSSRApp } from 'vue'
import pinia from './store'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
app.use(pinia)
return { app }
}
第二步:设计用户状态模块
// /store/modules/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { loginApi, getUserInfoApi } from '@/api/user'
export const useUserStore = defineStore('user', () => {
// 状态定义
const token = ref('')
const userInfo = ref(null)
const permissions = ref([])
// 计算属性
const isLogin = computed(() => !!token.value)
const hasPermission = computed(() => (permission) => {
return permissions.value.includes(permission)
})
// Actions
const login = async (credentials) => {
try {
const { token: authToken, user } = await loginApi(credentials)
token.value = authToken
userInfo.value = user
// 跨端存储token
uni.setStorageSync('auth_token', authToken)
// 获取用户权限
await fetchPermissions()
return { success: true }
} catch (error) {
console.error('登录失败:', error)
return { success: false, message: error.message }
}
}
const fetchPermissions = async () => {
if (!token.value) return
try {
const data = await getUserInfoApi(token.value)
permissions.value = data.permissions || []
} catch (error) {
console.error('获取权限失败:', error)
}
}
const logout = () => {
token.value = ''
userInfo.value = null
permissions.value = []
// 清理跨端存储
uni.removeStorageSync('auth_token')
uni.removeStorageSync('user_info')
// 跳转到登录页
uni.reLaunch({ url: '/pages/login/login' })
}
// 初始化时恢复状态
const initFromStorage = () => {
const storedToken = uni.getStorageSync('auth_token')
if (storedToken) {
token.value = storedToken
fetchPermissions()
}
}
return {
token,
userInfo,
permissions,
isLogin,
hasPermission,
login,
logout,
initFromStorage,
fetchPermissions
}
})
🏗️ 高级特性:跨端数据同步方案
🔄 实时同步Store
// /store/modules/sync.js
import { defineStore } from 'pinia'
import { ref, onUnmounted } from 'vue'
export const useSyncStore = defineStore('sync', () => {
const lastSyncTime = ref(0)
let syncTimer = null
// 启动定时同步
const startAutoSync = (interval = 30000) => {
if (syncTimer) clearInterval(syncTimer)
syncTimer = setInterval(async () => {
await syncData()
}, interval)
// 页面卸载时清理
onUnmounted(() => {
if (syncTimer) {
clearInterval(syncTimer)
syncTimer = null
}
})
}
// 跨端数据同步
const syncData = async () => {
// 检查网络状态
const networkType = await getNetworkType()
if (networkType === 'none') return
try {
// 同步用户数据
const userStore = useUserStore()
if (userStore.isLogin) {
await userStore.fetchPermissions()
}
// 同步应用配置
const appStore = useAppStore()
await appStore.fetchConfig()
lastSyncTime.value = Date.now()
} catch (error) {
console.error('数据同步失败:', error)
}
}
return { lastSyncTime, startAutoSync, syncData }
})
📱 多端适配器模式
// /utils/storage-adapter.js
class StorageAdapter {
constructor() {
this.platform = this.detectPlatform()
}
detectPlatform() {
// 检测运行平台
#ifdef MP-WEIXIN
return 'wechat'
#endif
#ifdef APP-PLUS
return 'app'
#endif
#ifdef H5
return 'h5'
#endif
return 'unknown'
}
async setItem(key, value) {
switch (this.platform) {
case 'wechat':
return wx.setStorageSync(key, value)
case 'app':
return plus.storage.setItem(key, JSON.stringify(value))
case 'h5':
localStorage.setItem(key, JSON.stringify(value))
break
}
}
async getItem(key) {
let value = null
switch (this.platform) {
case 'wechat':
value = wx.getStorageSync(key)
break
case 'app':
const raw = plus.storage.getItem(key)
value = raw ? JSON.parse(raw) : null
break
case 'h5':
const stored = localStorage.getItem(key)
value = stored ? JSON.parse(stored) : null
break
}
return value
}
// 统一清理方法
clearByPrefix(prefix) {
// 各平台实现清理逻辑
}
}
export default new StorageAdapter()
🔧 实战案例:购物车状态管理
购物车Store实现
// /store/modules/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { debounce } from 'lodash-es'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const selectedItems = ref(new Set())
// 计算总价
const totalPrice = computed(() => {
return items.value.reduce((sum, item) => {
return sum + (item.price * item.quantity)
}, 0)
})
// 计算选中商品总价
const selectedTotalPrice = computed(() => {
return items.value.reduce((sum, item) => {
if (selectedItems.value.has(item.id)) {
return sum + (item.price * item.quantity)
}
return sum
}, 0)
})
// 添加商品(防抖处理)
const addItem = debounce((product, quantity = 1) => {
const existingIndex = items.value.findIndex(item => item.id === product.id)
if (existingIndex > -1) {
// 更新数量
items.value[existingIndex].quantity += quantity
} else {
// 新增商品
items.value.push({
...product,
quantity,
addedTime: Date.now()
})
}
// 保存到本地存储
saveToLocal()
// 显示添加成功提示
uni.showToast({
title: '已加入购物车',
icon: 'success'
})
}, 300)
// 更新商品数量
const updateQuantity = (itemId, quantity) => {
const item = items.value.find(item => item.id === itemId)
if (item) {
item.quantity = Math.max(1, quantity)
saveToLocal()
}
}
// 切换选中状态
const toggleSelect = (itemId) => {
if (selectedItems.value.has(itemId)) {
selectedItems.value.delete(itemId)
} else {
selectedItems.value.add(itemId)
}
}
// 全选/取消全选
const toggleSelectAll = () => {
if (selectedItems.value.size === items.value.length) {
selectedItems.value.clear()
} else {
items.value.forEach(item => {
selectedItems.value.add(item.id)
})
}
}
// 删除选中商品
const removeSelected = () => {
items.value = items.value.filter(item =>
!selectedItems.value.has(item.id)
)
selectedItems.value.clear()
saveToLocal()
}
// 本地存储
const saveToLocal = () => {
const cartData = {
items: items.value,
timestamp: Date.now()
}
uni.setStorageSync('cart_data', cartData)
}
// 从本地恢复
const restoreFromLocal = () => {
const saved = uni.getStorageSync('cart_data')
if (saved && saved.items) {
// 检查是否过期(7天)
const isExpired = Date.now() - saved.timestamp > 7 * 24 * 60 * 60 * 1000
if (!isExpired) {
items.value = saved.items
} else {
// 清理过期数据
uni.removeStorageSync('cart_data')
}
}
}
// 清空购物车
const clearCart = () => {
items.value = []
selectedItems.value.clear()
uni.removeStorageSync('cart_data')
}
return {
items,
selectedItems,
totalPrice,
selectedTotalPrice,
addItem,
updateQuantity,
toggleSelect,
toggleSelectAll,
removeSelected,
restoreFromLocal,
clearCart
}
})
在页面中使用购物车Store
<template>
<view class="cart-page">
<view class="cart-header">
<text>购物车({{ cartStore.items.length }})</text>
<button @click="cartStore.toggleSelectAll">
{{ cartStore.selectedItems.size === cartStore.items.length ? '取消全选' : '全选' }}
</button>
</view>
<scroll-view scroll-y style="height: 500rpx;">
<view v-for="item in cartStore.items" :key="item.id" class="cart-item">
<checkbox :checked="cartStore.selectedItems.has(item.id)"
@click="cartStore.toggleSelect(item.id)" />
<image :src="item.image" mode="aspectFill"></image>
<view class="item-info">
<text class="title">{{ item.name }}</text>
<text class="price">¥{{ item.price }}</text>
<view class="quantity-control">
<button @click="cartStore.updateQuantity(item.id, item.quantity - 1)">-</button>
<text>{{ item.quantity }}</text>
<button @click="cartStore.updateQuantity(item.id, item.quantity + 1)">+</button>
</view>
</view>
</view>
</scroll-view>
<view class="cart-footer">
<text>合计:¥{{ cartStore.selectedTotalPrice }}</text>
<button @click="checkout" :disabled="cartStore.selectedItems.size === 0">
结算({{ cartStore.selectedItems.size }})
</button>
</view>
</view>
</template>
<script setup>
import { useCartStore } from '@/store/modules/cart'
import { onLoad } from '@dcloudio/uni-app'
const cartStore = useCartStore()
onLoad(() => {
// 页面加载时恢复购物车数据
cartStore.restoreFromLocal()
})
const checkout = () => {
if (cartStore.selectedItems.size === 0) {
uni.showToast({ title: '请选择商品', icon: 'none' })
return
}
// 跳转到结算页面
uni.navigateTo({
url: '/pages/checkout/checkout'
})
}
</script>
🎯 性能优化与最佳实践
1. 状态分片加载
大型应用按模块延迟加载Store,减少初始包体积
2. 内存管理
及时清理不再使用的状态,避免内存泄漏
3. 数据持久化策略
重要数据持久化,临时数据内存存储
4. 错误边界处理
Store操作添加try-catch,保证应用稳定性
调试技巧
// 开发环境启用严格模式
import { createPinia } from 'pinia'
const pinia = createPinia()
// #ifdef H5
// H5环境使用Pinia Devtools
if (process.env.NODE_ENV === 'development') {
pinia.use(({ store }) => {
// 监听状态变化
store.$subscribe((mutation, state) => {
console.log(`[Pinia] ${mutation.storeId} changed:`, mutation, state)
})
})
}
// #endif
// 自定义调试方法
export function debugStore(storeName) {
const store = useStore(storeName)
console.log(`[${storeName}] Current state:`, store.$state)
return store
}
📚 项目结构建议
src/
├── store/
│ ├── index.js # Pinia实例配置
│ ├── modules/ # 模块化Store
│ │ ├── user.js # 用户状态
│ │ ├── cart.js # 购物车状态
│ │ ├── app.js # 应用配置
│ │ ├── sync.js # 同步状态
│ │ └── index.js # 模块统一导出
│ └── plugins/ # Pinia插件
│ ├── persistence.js # 持久化插件
│ └── logger.js # 日志插件
├── utils/
│ ├── storage-adapter.js # 存储适配器
│ └── store-helper.js # Store工具函数
└── composables/ # 组合式函数
├── useStoreSync.js # 状态同步逻辑
└── useStoreCache.js # 缓存管理
下一步学习建议
- 深入学习Pinia插件开发,定制业务需求
- 研究UniApp多端存储的底层原理
- 探索状态管理与服务端渲染(SSR)的结合
- 学习性能监控和错误追踪方案

