uni-app跨平台状态管理实战:构建可扩展的模块化商城应用

2026-05-14 0 670

uni-app作为一款跨平台框架,一套代码可发布到iOS、Android、H5以及各种小程序。然而,随着应用规模增长,状态管理和代码组织成为挑战。本文通过构建一个模块化商城应用,完整演示如何使用Vuex进行状态管理,并结合分包策略优化性能,同时保持跨平台一致性。

一、为什么需要模块化状态管理?

在uni-app中,页面间共享数据(如用户登录态、购物车)是常见需求。如果每个页面独立管理数据,会导致数据不一致和代码冗余。Vuex提供了集中式存储,而模块化(Module)则让状态管理按业务领域拆分,例如:

  • user模块:用户信息、登录状态
  • cart模块:购物车商品、数量
  • product模块:商品列表、分类

这样每个模块独立维护自己的state、mutations、actions,避免命名冲突,也便于团队协作。

二、项目结构设计

我们采用uni-app官方推荐的目录结构,结合模块化Vuex:

┌─ components            # 公共组件
│   ├─ product-card.vue
│   └─ cart-badge.vue
├─ pages                 # 页面
│   ├─ index             # 首页
│   ├─ category          # 分类页
│   ├─ cart              # 购物车页
│   └─ mine              # 个人中心
├─ store                 # Vuex状态管理
│   ├─ index.js          # 主入口
│   ├─ modules
│   │   ├─ user.js       # 用户模块
│   │   ├─ cart.js       # 购物车模块
│   │   └─ product.js    # 商品模块
│   └─ getters.js        # 全局getters
├─ api                   # API请求封装
│   └─ index.js
├─ utils                 # 工具函数
├─ static                # 静态资源
├─ App.vue
├─ main.js
├─ manifest.json
└─ pages.json
    

三、Vuex模块化实现

1. 用户模块(store/modules/user.js)

export default {
    namespaced: true, // 启用命名空间
    state: {
        token: '',
        userInfo: null,
        isLogin: false
    },
    getters: {
        // 获取用户昵称,未登录显示默认
        displayName: (state) => {
            return state.userInfo?.nickname || '未登录用户'
        }
    },
    mutations: {
        SET_TOKEN(state, token) {
            state.token = token
            state.isLogin = !!token
        },
        SET_USER_INFO(state, info) {
            state.userInfo = info
        },
        LOGOUT(state) {
            state.token = ''
            state.userInfo = null
            state.isLogin = false
            // 清除本地存储
            uni.removeStorageSync('token')
        }
    },
    actions: {
        // 模拟登录
        async login({ commit }, { username, password }) {
            // 实际项目调用api
            const res = await new Promise((resolve) => {
                setTimeout(() => {
                    resolve({
                        token: 'mock_token_' + Date.now(),
                        userInfo: { nickname: username, avatar: '' }
                    })
                }, 500)
            })
            commit('SET_TOKEN', res.token)
            commit('SET_USER_INFO', res.userInfo)
            uni.setStorageSync('token', res.token)
            return res
        },
        // 检查登录状态(从本地存储恢复)
        async checkLogin({ commit }) {
            const token = uni.getStorageSync('token')
            if (token) {
                commit('SET_TOKEN', token)
                // 可以在这里请求用户信息
                commit('SET_USER_INFO', { nickname: '已登录用户', avatar: '' })
            }
        }
    }
}
    

2. 购物车模块(store/modules/cart.js)

export default {
    namespaced: true,
    state: {
        items: [] // { id, name, price, quantity, image }
    },
    getters: {
        totalCount: (state) => {
            return state.items.reduce((sum, item) => sum + item.quantity, 0)
        },
        totalPrice: (state) => {
            return state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
        }
    },
    mutations: {
        ADD_ITEM(state, product) {
            const existing = state.items.find(item => item.id === product.id)
            if (existing) {
                existing.quantity += product.quantity || 1
            } else {
                state.items.push({
                    ...product,
                    quantity: product.quantity || 1
                })
            }
        },
        REMOVE_ITEM(state, productId) {
            state.items = state.items.filter(item => item.id !== productId)
        },
        UPDATE_QUANTITY(state, { id, quantity }) {
            const item = state.items.find(item => item.id === id)
            if (item) {
                item.quantity = quantity
            }
        },
        CLEAR_CART(state) {
            state.items = []
        }
    },
    actions: {
        addToCart({ commit }, product) {
            commit('ADD_ITEM', product)
            // 可以在这里显示提示
            uni.showToast({ title: '已加入购物车', icon: 'success' })
        }
    }
}
    

3. 主入口(store/index.js)

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'
import product from './modules/product'
import getters from './getters'

Vue.use(Vuex)

const store = new Vuex.Store({
    modules: {
        user,
        cart,
        product
    },
    getters
})

export default store
    

四、页面中使用状态管理

1. 首页商品列表(pages/index/index.vue)

<template>
    <view class="container">
        <view class="product-grid">
            <view v-for="item in productList" :key="item.id" class="product-card" @click="goDetail(item)">
                <image :src="item.image" mode="aspectFill" class="product-image"></image>
                <view class="product-info">
                    <text class="product-name">{{ item.name }}</text>
                    <text class="product-price">¥{{ item.price }}</text>
                    <button type="primary" size="mini" @click.stop="addToCart(item)">加入购物车</button>
                </view>
            </view>
        </view>
    </view>
</template>

<script>
import { mapState, mapActions } from 'vuex'
export default {
    computed: {
        ...mapState('product', ['productList'])
    },
    methods: {
        ...mapActions('cart', ['addToCart']),
        goDetail(product) {
            uni.navigateTo({ url: `/pages/detail/detail?id=${product.id}` })
        }
    },
    onLoad() {
        // 触发商品模块的加载动作
        this.$store.dispatch('product/loadProducts')
    }
}
</script>
    

2. 购物车页面(pages/cart/cart.vue)

<template>
    <view class="cart-page">
        <view v-if="items.length === 0" class="empty-cart">
            <text>购物车是空的</text>
        </view>
        <view v-else>
            <view v-for="item in items" :key="item.id" class="cart-item">
                <image :src="item.image" mode="aspectFill" class="item-image"></image>
                <view class="item-info">
                    <text class="item-name">{{ item.name }}</text>
                    <text class="item-price">¥{{ item.price * item.quantity }}</text>
                    <view class="quantity-control">
                        <button size="mini" @click="decrease(item)">-</button>
                        <text>{{ item.quantity }}</text>
                        <button size="mini" @click="increase(item)">+</button>
                    </view>
                </view>
                <button type="warn" size="mini" @click="remove(item.id)">删除</button>
            </view>
            <view class="cart-footer">
                <text>合计: ¥{{ totalPrice }}</text>
                <button type="primary" @click="checkout">结算</button>
            </view>
        </view>
    </view>
</template>

<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
    computed: {
        ...mapState('cart', ['items']),
        ...mapGetters('cart', ['totalPrice', 'totalCount'])
    },
    methods: {
        ...mapMutations('cart', ['UPDATE_QUANTITY', 'REMOVE_ITEM', 'CLEAR_CART']),
        increase(item) {
            this.UPDATE_QUANTITY({ id: item.id, quantity: item.quantity + 1 })
        },
        decrease(item) {
            if (item.quantity > 1) {
                this.UPDATE_QUANTITY({ id: item.id, quantity: item.quantity - 1 })
            } else {
                this.REMOVE_ITEM(item.id)
            }
        },
        remove(id) {
            this.REMOVE_ITEM(id)
            uni.showToast({ title: '已删除', icon: 'none' })
        },
        checkout() {
            // 跳转到结算页
            uni.navigateTo({ url: '/pages/checkout/checkout' })
        }
    }
}
</script>
    

五、模块化分包策略

uni-app支持分包加载,将不同业务模块的页面放在不同分包中,减少首屏加载体积。我们在pages.json中配置:

{
    "pages": [
        {"path": "pages/index/index", "style": {}},
        {"path": "pages/category/category", "style": {}},
        {"path": "pages/cart/cart", "style": {}},
        {"path": "pages/mine/mine", "style": {}}
    ],
    "subPackages": [
        {
            "root": "subpackages/product",
            "pages": [
                {"path": "detail/detail", "style": {}},
                {"path": "search/search", "style": {}}
            ]
        },
        {
            "root": "subpackages/order",
            "pages": [
                {"path": "list/list", "style": {}},
                {"path": "detail/detail", "style": {}}
            ]
        }
    ],
    "preloadRule": {
        "pages/index/index": {
            "network": "all",
            "packages": ["subpackages/product"]
        }
    }
}
    

这样,商品详情和搜索页面在需要时才加载,而首页预加载了商品分包,提升用户体验。

六、跨平台适配技巧

  • 条件编译:使用#ifdef#ifndef处理平台差异,例如微信小程序登录与App端登录逻辑不同。
  • API封装:将uni-app的API(如uni.request)封装在api/index.js中,便于统一处理错误和token。
  • 自定义导航栏:使用uni-nav-bar组件并配置titleNView,保持各平台一致性。
// api/index.js
const request = (url, data = {}, method = 'GET') => {
    return new Promise((resolve, reject) => {
        uni.request({
            url: 'https://api.example.com' + url,
            data,
            method,
            header: {
                'Authorization': uni.getStorageSync('token') || ''
            },
            success: (res) => {
                if (res.data.code === 0) {
                    resolve(res.data.data)
                } else {
                    reject(res.data.message)
                }
            },
            fail: reject
        })
    })
}
    

七、性能优化建议

  • 合理使用getters:对于计算密集型数据(如购物车总价),使用getters缓存结果。
  • 避免频繁commit:批量操作时使用action封装多个mutation。
  • 分包预加载:通过preloadRule预加载用户可能访问的分包。
  • 图片懒加载:使用lazy-load属性或IntersectionObserver。

八、总结

通过模块化状态管理和分包策略,我们构建了一个结构清晰、性能优化的uni-app商城应用。Vuex的命名空间模块让代码易于维护,分包加载让首屏速度更快。这套架构可以轻松扩展到更大的项目,同时保持跨平台一致性。

uni-app的生态日益成熟,掌握这些高级技巧,能让你在跨平台开发中游刃有余。现在就去重构你的项目吧!


本文为原创技术教程,代码基于uni-app 3.x和Vuex 3.x。建议在实际项目中结合HBuilderX进行调试。

uni-app跨平台状态管理实战:构建可扩展的模块化商城应用
收藏 (0) 打赏

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

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

淘吗网 uniapp uni-app跨平台状态管理实战:构建可扩展的模块化商城应用 https://www.taomawang.com/web/uniapp/1793.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

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

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