UniApp Pinia 全局状态管理实战:优雅实现跨页面数据同步与持久化

2026-05-28 0 977

UniApp开发中,随着业务复杂度提升,跨页面共享用户登录状态、购物车数量、主题设置等全局数据变得至关重要。Vue 2时代我们常使用Vuex,但Vue 3官方推荐的替代品Pinia凭借其简洁的API、完整的TypeScript支持和更优的tree-shaking表现,已成为新一代状态管理的首选。UniApp对Pinia的集成非常友好,本文将手把手带你从安装到实战,构建一个可持久化的购物车状态管理系统,让数据在页面间无缝流转,并且在App重启后依然保留。

一、在UniApp项目中集成Pinia

首先,确保你的UniApp项目基于Vue 3版本(CLI或HBuilderX创建时选择Vue3)。安装Pinia非常简单,在项目根目录执行:

npm install pinia

或者使用yarn:

yarn add pinia

安装完成后,在 main.js 中注册Pinia插件:

// main.js
import App from './App'
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'

export function createApp() {
    const app = createSSRApp(App)
    const pinia = createPinia()
    app.use(pinia)
    return {
        app,
        pinia
    }
}

注意UniApp使用 createSSRApp 来支持服务端渲染兼容(H5端),因此我们需要导出app和pinia。之后,你就可以在任何页面的 setup 函数中使用 useStore() 了。

二、定义第一个Store:管理用户信息

Pinia的Store分为两种模式:Options Store(类似Vuex模块)和Setup Store(利用Vue组合式API)。这里我们采用更灵活的Setup Store风格。在项目 store/ 目录下创建 user.js

// store/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
    // 状态
    const token = ref('')
    const userInfo = ref({
        nickname: '',
        avatar: ''
    })

    // 计算属性
    const isLogin = computed(() => !!token.value)

    // 动作
    function login(loginData) {
        // 模拟登录API请求
        return new Promise((resolve) => {
            setTimeout(() => {
                token.value = 'sample-token-' + Date.now()
                userInfo.value = {
                    nickname: loginData.username || '用户',
                    avatar: '/static/default-avatar.png'
                }
                resolve(true)
            }, 800)
        })
    }

    function logout() {
        token.value = ''
        userInfo.value = { nickname: '', avatar: '' }
    }

    return {
        token,
        userInfo,
        isLogin,
        login,
        logout
    }
})

Setup Store 直接利用 refcomputed 定义状态和计算属性,返回值就是暴露给外部使用的数据和方法。相比Options Store,它更加自由且易于扩展。

三、在页面中使用Store:用户登录与状态显示

创建登录页面,调用Store的 login 动作,并在个人中心页面展示用户信息。你可以在任何组件或页面中通过 useUserStore() 获取同一份状态。

登录页 pages/login/login.vue

<template>
    <view class="page">
        <input v-model="username" placeholder="请输入用户名" />
        <button @click="doLogin">登录</button>
        <text v-if="userStore.isLogin">已登录:{{ userStore.userInfo.nickname }}</text>
    </view>
</template>

<script setup>
import { ref } from 'vue';
import { useUserStore } from '@/store/user.js';

const userStore = useUserStore();
const username = ref('');

function doLogin() {
    userStore.login({ username: username.value }).then(() => {
        uni.showToast({ title: '登录成功' });
        uni.switchTab({ url: '/pages/mine/mine' });
    });
}
</script>

个人中心页 pages/mine/mine.vue

<template>
    <view class="page">
        <image :src="userStore.userInfo.avatar" />
        <text>{{ userStore.userInfo.nickname }}</text>
        <button v-if="userStore.isLogin" @click="userStore.logout()">退出登录</button>
        <button v-else @click="goLogin">去登录</button>
    </view>
</template>

<script setup>
import { useUserStore } from '@/store/user.js';
const userStore = useUserStore();

function goLogin() {
    uni.navigateTo({ url: '/pages/login/login' });
}
</script>

你会发现,登录成功后切换tab到个人中心,用户信息自动呈现。这一切都归功于Pinia的响应式机制——任何引用同一store的地方都会同步更新。不需要手动刷新或传递参数。

四、复杂场景:购物车状态管理

购物车是典型的全局状态场景:商品详情页可以添加商品,购物车页面展示列表并允许修改数量或移除,同时首页或底部导航栏上要实时显示购物车总数量。我们创建一个 cart.js store:

// store/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
    const items = ref([]) // { id, name, price, quantity, image }

    // 计算总价
    const totalPrice = computed(() => {
        return items.value.reduce((sum, item) => sum + item.price * item.quantity, 0).toFixed(2)
    })

    // 计算总数量
    const totalQuantity = computed(() => {
        return items.value.reduce((cnt, item) => cnt + item.quantity, 0)
    })

    // 添加商品
    function addItem(product) {
        const exist = items.value.find(i => i.id === product.id)
        if (exist) {
            exist.quantity++
        } else {
            items.value.push({
                id: product.id,
                name: product.name,
                price: product.price,
                image: product.image,
                quantity: 1
            })
        }
        // 每次修改后触发持久化(稍后实现)
    }

    // 更新数量
    function updateQuantity(productId, quantity) {
        const item = items.value.find(i => i.id === productId)
        if (item) {
            item.quantity = quantity > 0 ? quantity : 1
        }
    }

    // 移除商品
    function removeItem(productId) {
        const index = items.value.findIndex(i => i.id === productId)
        if (index !== -1) {
            items.value.splice(index, 1)
        }
    }

    // 清空购物车
    function clearCart() {
        items.value = []
    }

    return {
        items,
        totalPrice,
        totalQuantity,
        addItem,
        updateQuantity,
        removeItem,
        clearCart
    }
})

在商品详情页加入购物车:

// pages/goods/detail.vue
<template>
    <view>
        <text>{{ product.name }}</text>
        <button @click="addToCart">加入购物车</button>
    </view>
</template>

<script setup>
import { reactive } from 'vue';
import { useCartStore } from '@/store/cart.js';

const cartStore = useCartStore();
const product = reactive({
    id: 1001,
    name: '无线蓝牙耳机',
    price: 199.00,
    image: '/static/headphone.png'
});

function addToCart() {
    cartStore.addItem(product);
    uni.showToast({ title: '已加入购物车' });
}
</script>

在底部导航栏或自定义tabbar上显示购物车数量:

<template>
    <view class="tabbar">
        <text>首页</text>
        <text>购物车({{ cartStore.totalQuantity }})</text>
        <text>我的</text>
    </view>
</template>

<script setup>
import { useCartStore } from '@/store/cart.js';
const cartStore = useCartStore();
</script>

购物车数量的变化会即时反映在任何引用该store的UI组件上,完全响应式。

五、数据持久化:让状态在App重启后依然存在

目前的状态仅保存在内存中,当用户关闭小程序或退出App后就会丢失。我们需要将Pinia的状态自动同步到 uni.storage 中,并在应用启动时重新加载。这可以通过Pinia的插件机制优雅实现。

store/ 目录下创建 persist.js 插件:

// store/plugins/persist.js
export function persistPlugin(context) {
    const { store } = context;

    // 应用启动时从storage恢复状态
    const key = `pinia-${store.$id}`
    const saved = uni.getStorageSync(key)
    if (saved) {
        store.$patch(saved)
    }

    // 每次状态变化后保存到storage(可添加防抖优化)
    store.$subscribe((mutation, state) => {
        uni.setStorageSync(key, JSON.parse(JSON.stringify(state)))
    })
}

然后在 main.js 中启用这个插件:

import { createPinia } from 'pinia'
import { persistPlugin } from './store/plugins/persist.js'

const pinia = createPinia()
pinia.use(persistPlugin)

这样,无论是用户信息还是购物车列表,都会自动同步到本地存储中。App冷启动后,状态依然保留。需要注意的是,uni.setStorageSync 有大小限制(一般为10MB),复杂对象需要序列化,我们通过 JSON.parse(JSON.stringify(state)) 确保只存储可序列化的数据。

对于需要更精细持久化控制的场景(比如只持久化某些字段),你也可以使用社区插件 pinia-plugin-persistedstate,它同样兼容UniApp,只需将 storage 选项指向 uni 的存储对象即可。

六、Pinia在UniApp中的注意事项

  • store实例共享:在UniApp中,每个页面是独立的,但Pinia store在应用级别是单例的,所以可以放心跨页面共享。
  • 条件编译:如果某些平台有特殊需求,可以在store内使用条件编译,例如 // #ifdef MP-WEIXIN
  • 组合式API与选项式API:Pinia完全兼容选项式写法,你可以在 methods 中通过 this.cartStore = useCartStore() 访问(需在 setup() 中返回)。
  • TypeScript支持:Pinia天生对TypeScript友好,无需额外类型声明即可获得完整的类型推断。

七、完整购物车页面演示

最后,我们给出一个购物车页面的完整代码,展示如何结合Pinia实现列表渲染、数量加减和总价计算。

<template>
    <view class="cart-page">
        <view v-if="cartStore.items.length === 0">购物车为空</view>
        <view v-else>
            <view v-for="item in cartStore.items" :key="item.id">
                <image :src="item.image" />
                <text>{{ item.name }}</text>
                <text>¥{{ item.price }}</text>
                <view>
                    <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>
                <button @click="cartStore.removeItem(item.id)">删除</button>
            </view>
            <view>合计:¥{{ cartStore.totalPrice }}</view>
            <button @click="cartStore.clearCart()">清空购物车</button>
        </view>
    </view>
</template>

<script setup>
import { useCartStore } from '@/store/cart.js';
const cartStore = useCartStore();
</script>

至此,一个完全响应式、跨页面且可持久化的购物车模块构建完毕。任何页面对购物车的修改,都会立即反映到相关页面上,用户体验流畅且代码维护成本极低。

八、总结

通过本文的实战,你已经掌握在UniApp中集成Pinia、定义Setup Store、实现全局状态共享以及利用插件进行数据持久化的全套技能。Pinia的轻量和优雅,让状态管理不再是负担,而是提升开发效率的利器。无论是用户登录、购物车,还是主题切换、消息未读数等场景,你都可以用同样的模式快速实现。立即在你的UniApp项目中引入Pinia,感受跨页面数据同步带来的便捷吧。

UniApp Pinia 全局状态管理实战:优雅实现跨页面数据同步与持久化
收藏 (0) 打赏

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

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

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp UniApp Pinia 全局状态管理实战:优雅实现跨页面数据同步与持久化 https://www.taomawang.com/web/uniapp/2040.html

常见问题

相关文章

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

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