UniApp跨端状态管理深度实战:基于Pinia构建企业级应用架构指南

2026-01-19 0 966
免费资源下载

从零到一构建支持小程序、H5、App的多端状态管理解决方案,解决跨端数据同步核心难题

UniApp 3.0+
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)的结合
  • 学习性能监控和错误追踪方案

UniApp跨端状态管理深度实战:基于Pinia构建企业级应用架构指南
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 uniapp UniApp跨端状态管理深度实战:基于Pinia构建企业级应用架构指南 https://www.taomawang.com/web/uniapp/1547.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务