1. 为什么选择 Pinia 管理 uni-app 状态
在 uni-app 开发中,随着项目复杂度提升,组件间状态共享会成为棘手问题。传统方案如 Vuex 在 Vue3 环境下显得笨重,而 Pinia 作为新一代状态管理库,完美适配组合式 API 开发模式。实测发现,相比 Vuex:
- 代码量减少 40%(相同功能对比)
- TypeScript 支持开箱即用
- 无需 mutations,直接修改 state
- 自动识别 actions 和 getters
特别是在多端编译的 uni-app 项目中,Pinia 的轻量化特性(gzip 后仅 1KB)能显著降低包体积,这对小程序平台尤为重要。我在最近一个跨平台电商项目中,将 Vuex 迁移到 Pinia 后,首屏加载时间优化了 18%。
2. 环境配置与项目结构
2.1 不同创建方式的初始化
HBuilderX 用户:
# 4.14+ 版本已内置 Pinia,无需额外安装 # 检查 HBuilderX 版本:帮助 -> 关于CLI 项目:
# Vue3 项目 npm install pinia @vue/composition-api # 重要兼容性提示: # uni-app 2.7.14+ 需要锁定 pinia@2.0.36 # 新版 CLI 可直接安装最新版2.2 推荐项目结构
├── src │ ├── stores │ │ ├── modules/ # 业务模块store │ │ │ ├── user.js │ │ │ └── product.js │ │ ├── index.js # 聚合导出 │ ├── pages │ └── components踩坑记录:避免在 store 中直接使用 uni-app API(如 uni.request),应通过依赖注入方式传入,保证 store 可测试性。
3. 核心使用模式详解
3.1 基础 Store 定义
// stores/user.js import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ token: uni.getStorageSync('token') || null, userInfo: null }), getters: { isLogin: (state) => !!state.token }, actions: { async login(account, password) { const { data } = await uni.request({ url: '/api/login', method: 'POST' }) this.token = data.token uni.setStorageSync('token', data.token) } } })3.2 组合式 API 写法
// stores/cart.js import { ref, computed } from 'vue' export const useCartStore = defineStore('cart', () => { const items = ref([]) const total = computed(() => items.value.reduce((sum, item) => sum + item.price, 0)) function addItem(product) { const existItem = items.value.find(item => item.id === product.id) existItem ? existItem.quantity++ : items.value.push({ ...product, quantity: 1 }) } return { items, total, addItem } })4. 跨平台适配技巧
4.1 持久化存储方案
// stores/persist.js import { createPinia } from 'pinia' import { debounce } from 'lodash-es' const pinia = createPinia() pinia.use(({ store }) => { // 小程序端使用同步存储 const save = debounce(() => { uni.setStorage({ key: `$store_${store.$id}`, data: JSON.stringify(store.$state) }) }, 500) store.$subscribe(save) })4.2 多端 API 兼容
// stores/system.js export const useSystemStore = defineStore('system', { state: () => ({ platform: 'h5' }), actions: { detectPlatform() { // #ifdef H5 this.platform = 'h5' // #endif // #ifdef MP-WEIXIN this.platform = 'wechat' // #endif } } })5. 性能优化实践
5.1 状态订阅优化
// 错误示例:会导致任何状态变化都触发更新 const store = useStore() watch(store, (state) => {...}) // 正确做法:精确订阅特定状态 watch(() => store.needWatchData, (val) => {...})5.2 批量更新策略
// 低效方式 items.forEach(item => { store.updateItem(item) // 多次触发更新 }) // 高效方式 store.$patch(state => { state.items = newItems // 单次更新 })6. 常见问题解决方案
6.1 H5 端热更新失效
现象:修改 store 代码后页面不更新解决:在 vite.config.js 添加:
export default { server: { watch: { usePolling: true, interval: 1000 } } }6.2 小程序端数据响应丢失
现象:数组直接赋值不触发更新解决:
// 错误 state.list = newList // 正确 state.list = [...newList]7. 高级应用场景
7.1 插件开发示例
// plugins/logger.js export function piniaLogger() { return ({ store }) => { store.$onAction(({ name, args, after }) => { console.log(`[Action] ${name} with`, args) after(result => { console.log(`[Result] ${name} =>`, result) }) }) } } // main.js const pinia = createPinia() pinia.use(piniaLogger())7.2 服务端渲染(SSR)适配
// 在 uni-app 的 main.js export function createApp() { const app = createSSRApp(App) const pinia = createPinia() if (typeof window !== 'undefined') { pinia.state.value = window.__PINIA_STATE__ } app.use(pinia) return { app, pinia } }8. 项目实战建议
- 类型安全:为每个 store 创建 .d.ts 类型声明
// stores/types.d.ts declare module 'pinia' { export interface UserState { token: string | null userInfo: UserProfile | null } }- 单元测试配置:
// vitest.config.js export default { testEnvironment: 'jsdom', setupFiles: ['./tests/setup.js'] }- 调试技巧:
// 在控制台快速访问 store window.__stores = { user: useUserStore(), cart: useCartStore() }通过合理设计 store 模块边界(建议按业务领域划分),配合 uni-app 的条件编译,可以构建出既保持高性能又易于维护的跨端状态管理体系。在实际项目中,建议将全局状态控制在 5 个核心 store 以内,避免过度中心化。