Uniapp Pinia状态管理实战:模块化设计与跨页面数据共享

2026-06-12 0 512

一、引言:为什么选择Pinia而不是Vuex

在Uniapp开发中,随着业务复杂度上升,跨组件和跨页面的数据共享成为核心挑战。虽然Vuex曾是官方推荐的状态管理库,但Pinia凭借其简洁的API、完整的TypeScript支持以及模块化设计,已经成为Vue生态的新标准。Pinia去除了mutations,直接通过actions修改状态,配合Uniapp的多端特性,能够极大提升开发效率和代码可维护性。本文将带你从零搭建一套基于Pinia的模块化状态管理体系,覆盖用户认证、购物车和全局配置三个典型模块,并展示数据持久化与跨页面响应式通信的完整实现。

二、项目初始化与Pinia安装

在已有的Uniapp项目(基于Vue3)中安装Pinia。通过HBuilderX的终端或命令行执行:

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以支持服务端渲染环境,Pinia的注册方式与标准Vue3一致。至此,Pinia已经可以在所有页面和组件中使用了。

三、模块化Store设计:拆分用户、购物车与全局配置

合理的模块划分是状态管理的关键。我们创建三个独立的Store模块,每个模块专注一个业务领域。在项目根目录下创建store文件夹,内含各模块文件。

3.1 用户认证模块 user.js

// store/user.js
import { defineStore } from 'pinia'
import { loginApi, getUserInfoApi } from '@/api/user.js'

export const useUserStore = defineStore('user', {
    state: () => ({
        token: uni.getStorageSync('user_token') || '',
        userInfo: null,
        isLogin: false
    }),
    getters: {
        userId: (state) => state.userInfo?.id,
        nickName: (state) => state.userInfo?.nickname || '未登录'
    },
    actions: {
        async login(username, password) {
            try {
                const res = await loginApi({ username, password })
                this.token = res.token
                this.isLogin = true
                uni.setStorageSync('user_token', res.token)
                await this.fetchUserInfo()
                return true
            } catch (e) {
                console.error('登录失败', e)
                return false
            }
        },
        async fetchUserInfo() {
            if (!this.token) return
            try {
                const res = await getUserInfoApi()
                this.userInfo = res.data
                this.isLogin = true
            } catch (e) {
                this.logout()
            }
        },
        logout() {
            this.token = ''
            this.userInfo = null
            this.isLogin = false
            uni.removeStorageSync('user_token')
            uni.reLaunch({ url: '/pages/login/login' })
        },
        // 初始化时检查本地token并获取用户信息
        async initAuth() {
            if (this.token) {
                await this.fetchUserInfo()
            }
        }
    }
})

3.2 购物车模块 cart.js

// store/cart.js
import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', {
    state: () => ({
        items: JSON.parse(uni.getStorageSync('cart_items') || '[]')
    }),
    getters: {
        totalCount: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
        totalPrice: (state) => state.items.reduce((sum, item) => sum + item.price * item.quantity, 0).toFixed(2),
        checkedItems: (state) => state.items.filter(item => item.checked)
    },
    actions: {
        addItem(product, quantity = 1) {
            const exist = this.items.find(item => item.id === product.id)
            if (exist) {
                exist.quantity += quantity
            } else {
                this.items.push({ ...product, quantity, checked: true })
            }
            this.saveToStorage()
        },
        removeItem(productId) {
            this.items = this.items.filter(item => item.id !== productId)
            this.saveToStorage()
        },
        updateQuantity(productId, quantity) {
            const item = this.items.find(item => item.id === productId)
            if (item) {
                item.quantity = quantity
                this.saveToStorage()
            }
        },
        toggleCheck(productId) {
            const item = this.items.find(item => item.id === productId)
            if (item) {
                item.checked = !item.checked
                this.saveToStorage()
            }
        },
        clearCart() {
            this.items = []
            uni.removeStorageSync('cart_items')
        },
        saveToStorage() {
            uni.setStorageSync('cart_items', JSON.stringify(this.items))
        }
    }
})

3.3 全局配置模块 app.js

// store/app.js
import { defineStore } from 'pinia'

export const useAppStore = defineStore('app', {
    state: () => ({
        theme: uni.getStorageSync('app_theme') || 'light',
        language: uni.getStorageSync('app_language') || 'zh-CN',
        systemInfo: null
    }),
    getters: {
        isDark: (state) => state.theme === 'dark'
    },
    actions: {
        setTheme(theme) {
            this.theme = theme
            uni.setStorageSync('app_theme', theme)
            // 可在此处调用全局主题切换逻辑
        },
        setLanguage(lang) {
            this.language = lang
            uni.setStorageSync('app_language', lang)
            // 可选:刷新当前页面以应用新语言
        },
        fetchSystemInfo() {
            this.systemInfo = uni.getSystemInfoSync()
        }
    }
})

上述模块各自独立,通过defineStore的第一个参数作为唯一标识。Getter用于派生状态,Actions处理异步请求或复杂逻辑。购物车模块直接通过uni.setStorageSync实现了简单的数据持久化,保证App重启后数据不丢失。

四、在页面和组件中使用Store

Pinia在Uniapp中的使用与Vue3组件完全一致,通过解构storeToRefs可以保持响应性。以下展示登录页面和购物车列表页面的实践。

4.1 登录页面调用用户Store

<template>
    <view>
        <input v-model="username" placeholder="用户名" />
        <input v-model="password" type="password" placeholder="密码" />
        <button @click="handleLogin">登录</button>
    </view>
</template>

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

const userStore = useUserStore()
const { isLogin, nickName } = storeToRefs(userStore)

const username = ref('')
const password = ref('')

const handleLogin = async () => {
    const success = await userStore.login(username.value, password.value)
    if (success) {
        uni.switchTab({ url: '/pages/index/index' })
    } else {
        uni.showToast({ title: '登录失败', icon: 'none' })
    }
}
</script>

4.2 购物车页面动态显示

<template>
    <view>
        <view v-for="item in cartItems" :key="item.id">
            <text>{{ item.name }} x {{ item.quantity }}</text>
            <text>¥{{ item.price * item.quantity }}</text>
            <button @click="removeItem(item.id)">删除</button>
        </view>
        <view>总计:¥{{ totalPrice }}({{ totalCount }}件)</view>
    </view>
</template>

<script setup>
import { useCartStore } from '@/store/cart.js'
import { storeToRefs } from 'pinia'

const cartStore = useCartStore()
const { items: cartItems, totalCount, totalPrice } = storeToRefs(cartStore)

const removeItem = (id) => {
    cartStore.removeItem(id)
}
</script>

通过storeToRefs解构的状态仍然是响应式的,而actions方法可以直接调用。跨页面通信变得异常简单:用户在商品详情页添加购物车后,购物车页面会立即更新,无需任何事件总线或手动同步。

五、实现跨页面实时通信与数据同步

在多页面场景下,例如“详情页”修改商品数量后返回“列表页”,或者“结算页”清空购物车后跳转“首页”,这些跨页面操作都通过Store自动同步。因为Pinia的State是全局单例,不同页面引用同一个Store实例,任何修改都会立即反映到所有使用该Store的组件和页面中。

若需要在页面间传递临时数据(如表单草稿),同样可以建立一个专门的临时Store,而无需通过URL参数传递大量数据。示例:

// store/temp.js
import { defineStore } from 'pinia'

export const useTempStore = defineStore('temp', {
    state: () => ({
        draftOrder: null
    }),
    actions: {
        setDraft(order) {
            this.draftOrder = order
        },
        clearDraft() {
            this.draftOrder = null
        }
    }
})

在订单填写页设置草稿,在支付确认页读取,提交后清除。全程无需URL参数或本地存储,数据隔离且安全。

六、数据持久化方案与插件扩展

虽然我们在模块内部手动调用了uni.setStorageSync,但对于大型项目,这些重复代码会造成负担。可以编写一个自定义Pinia插件,自动将指定Store的状态同步到本地存储。

// plugins/pinia-persist.js
export function createPersistPlugin(options = {}) {
    const { key = 'pinia_state', paths = [] } = options
    return ({ store }) => {
        // 从本地存储恢复状态
        const savedState = uni.getStorageSync(key)
        if (savedState) {
            try {
                const parsed = JSON.parse(savedState)
                store.$patch(parsed)
            } catch (e) {}
        }
        // 订阅状态变化并存储
        store.$subscribe((mutation, state) => {
            let dataToStore = state
            if (paths.length > 0) {
                dataToStore = {}
                paths.forEach(path => {
                    dataToStore[path] = state[path]
                })
            }
            uni.setStorageSync(key, JSON.stringify(dataToStore))
        })
    }
}

在创建Pinia实例时注册该插件:

const pinia = createPinia()
pinia.use(createPersistPlugin({ key: 'user_store', paths: ['token', 'userInfo'] }))

这样,指定的Store路径会自动持久化,避免了在每个Action中手动编写存储逻辑。当然,在Uniapp中需要注意各平台存储限制和同步策略。

七、注意事项与最佳实践

  • 避免在Getter中执行副作用:Getter应是纯函数,只用于计算派生状态,不要在Getter里修改State或发起API请求。
  • 大型列表性能优化:购物车等列表数据如果过多,应考虑分页或虚拟列表,Store本身无性能瓶颈,但页面渲染会受影响。
  • 模块间通信:一个Store的Action中可以调用另一个Store的Action,通过引入对应的useStore即可,但应注意避免循环依赖。
  • TypeScript支持:Pinia完美支持TypeScript,建议为每个State定义接口,提升代码健壮性和IDE提示。
  • 与Uniapp生命周期配合:可在onLaunchonShow中调用Store的初始化方法,如userStore.initAuth(),确保应用启动时恢复登录态。
  • 清理敏感数据:用户退出登录时,应清理所有相关Store的状态,避免残留token被滥用。

八、完整示例:App启动时的初始化流程

App.vue中,我们可以集中执行各模块的初始化动作:

<script setup>
import { onLaunch } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/user.js'
import { useAppStore } from '@/store/app.js'

onLaunch(async () => {
    const appStore = useAppStore()
    appStore.fetchSystemInfo()
    
    const userStore = useUserStore()
    await userStore.initAuth()
    // 根据登录状态决定跳转页面
    if (!userStore.isLogin) {
        uni.reLaunch({ url: '/pages/login/login' })
    }
})
</script>

这样,用户每次打开应用都会自动读取本地Token并获取最新用户信息,实现无感登录。同时系统信息也被缓存到Store中,全局可访问。

九、总结

Pinia为Uniapp带来了极简且强大的状态管理体验。通过模块化拆分,我们可以将用户、购物车、配置等不同领域的状态隔离管理,同时利用Store的全局特性实现跨页面无缝通信。结合本地存储插件,数据持久化也变得自动化。本文从安装配置到模块设计,再到页面使用和高级插件,完整演示了一套可投入生产的Pinia实践方案。在后续项目中,你可以根据业务需求在此基础之上继续扩展,享受Pinia带来的高效开发体验。

Uniapp Pinia状态管理实战:模块化设计与跨页面数据共享
收藏 (0) 打赏

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

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

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

淘吗网 uniapp Uniapp Pinia状态管理实战:模块化设计与跨页面数据共享 https://www.taomawang.com/web/uniapp/2135.html

常见问题

相关文章

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

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