一、为什么升级架构
v0.1.0 的 setup 式 + Ref 形态跑通了下限,但也撞上了天花板——两条平台硬约束:
- UTS 无 Proxy、无反射:store 对象不可动态挂方法、不可运行时枚举字段,
$patch/$subscribe/$onAction/$reset在 setup 式形态下全部无法实现(v0.1.0 只能裁掉) - 新版 HBuilderX 编译器对 UTS 插件内运行时类 vue 符号(
Ref/watch/effectScope)的 Kotlin import 生成失效——云打包 Kotlin 编译直接 Unresolved
而 reactive<T> 在 UTS 中是编译期展开(生成属性代理类,getter/setter 内联依赖追踪),不受上述约束影响。class + reactive 因此成为 uni-app x 插件实现完整 Pinia 功能面的唯一可行架构。v0.2.0 完成架构转向,一次补齐:
| 能力 | v0.1.0 | v0.2.0 |
|---|---|---|
| `$patch` / `$reset` | 顶层 `patch(mutator)` / ❌ | ✅ `$patch(partial)` / `$reset()`(`_doReset` 钩子) |
| `$subscribe` / `$onAction` | ❌(字段级 `watch` 替代) | ✅ 内置(快照分发 / `callAction` 追踪) |
| `$id` / `$state` / `$dispose` | 顶层函数映射 | ✅ 实例属性 / 方法 |
| `$save` 显式保存点 | — | ✅ |
| 持久化 | ❌ | ✅ 内置插件,零胶水 |
二、核心能力
class 式 defineStore
import { reactive } from 'vue'
import { defineStore, PiniaStoreBase } from '@/uni_modules/ux-pinia'
export type CounterState = {
count: number
name: string
}
export class CounterStore extends PiniaStoreBase {
// state:reactive<T> 强类型包裹(字段为原始值,模板 / 脚本直接访问)
state: CounterState = reactive<CounterState>({ count: 0, name: 'pinia' })
// 派生值:class getter(每次求值,模板经 reactive 属性依赖收集自动更新)
get doubleCount(): number {
return this.state.count * 2
}
// constructor:super() 后必须 bindState(this.state)($-API 与持久化的前提)
constructor() {
super()
this.bindState(this.state)
}
// actions = 普通 class method,直接修改 state
increment(): void {
this.state.count++
}
}
export const useCounterStore = defineStore<CounterStore>('counter', (): CounterStore => new CounterStore())
结构对齐 Pinia 官方 state / getters / actions 心智;模板事件绑定 class method 必须带括号(@click="counter.increment()",Kotlin 端 method 不是值)。
完整实例 API($-methods)
const counter = useCounterStore()
counter.$patch({ count: 10 } as UTSJSONObject) // 批量修改(经 _hydrate 钩子装载)
counter.$reset() // 重置(_doReset 钩子)
const unsub = counter.$subscribe((type: string, state: UTSJSONObject): void => {
// type = 'patch' | 'reset' | 'save';state = _serialize() 快照
})
counter.$onAction((info: ActionInfo): void => { /* action 追踪 */ })
counter.$save() // 显式保存点(direct 修改 state 后的落盘触发)
counter.$dispose() // 注销,下次 useStore() 重建
counter.$id // 'counter'
$subscribe 仅由 $patch / $reset / $save 触发(UTS 无全局订阅钩子);direct 修改 state 后的关键操作请调用 counter.$save()。
内置持久化插件(零胶水)
main.uts 顶层登记一次,全部 store 生效:
import { createPinia, setActivePinia, createPersistPlugin } from '@/uni_modules/ux-pinia'
setActivePinia(createPinia().use(createPersistPlugin({
keyPrefix: 'ux-pinia:',
includeStores: null, // null = 全部 store
excludeStores: new Array<string>(),
serializer: null // null = JSON 序列化
})))
需要持久化的 store 重写两个钩子(Template Method 契约):
override _serialize(): UTSJSONObject {
// 落盘快照:只含需持久化的字段
return { token: this.state.token } as UTSJSONObject
}
override _hydrate(data: UTSJSONObject): void {
// 恢复回填:字段级判空(存储损坏 / 字段缺失 → 保留初始值)
if (data['token'] != null) {
this.state.token = data['token'] as string
}
}
恢复在 store 创建时自动发生;落盘由 $patch / $reset / $save 自动触发。对比旧版「每字段两行 ref / watch 胶水」的方案,持久化成本降为两个声明式钩子。存储后端可经 setStorageAdapter 替换(默认 uni storage,可换 unix-storage / 原生 preferences);clearPersistedState(storeId, keyPrefix) 可清空单个 store。
免挂载单例与插件体系(一如既往)
模块级注册表保证应用级单例,跨页面共享状态,无需 app.use(实测原生端 app.use 不触发 install,免挂载是唯一双端一致的形态);插件 pinia.use(plugin) 链式注册,PiniaPluginContext(pinia / store / storeId)对齐标准 Pinia——持久化插件本身就是插件体系的第一个内置成员。
三、平台支持现状
| 平台 | 状态 |
|---|---|
| Web | ✅ 全用例通过(基础用法 / 响应式 / 单例跨页面 / 实例 API / 跨 store / 订阅 / 插件 / 持久化) |
| Android | ✅ 蒸汽模式(vapor)云打包 Kotlin 编译链通过,真机用例复验中 |
| iOS / 鸿蒙 | ⏳ 代码全端同构(纯 UTS、无平台分支),待实测 |
关键实测裁决(驱动本次架构定稿):reactive<T> 为编译期展开(Kotlin 生成正常);运行时类 vue 符号(Ref / ref / watch / effectScope)在插件内 Kotlin import 生成失效(effectScope 被生成器静默丢弃——派生值因此用 getter 而非 computed);模板 class method 绑定必须带括号;Kotlin 泛型擦除边界(error17)在库内收敛,业务侧零感知。
四、0.1.0 → 0.2.0 迁移对照
| v0.1.0(setup 式,已移除) | v0.2.0(class 式) |
|---|---|
| `type CounterStore = { count: Ref | `class CounterStore extends PiniaStoreBase`(state = `reactive |
| 脚本 `store.count.value` / 模板 `storeToRefs` 解构 | `store.state.count` 直接读写,脚本模板同路径 |
| `patch(store, fn)` | `store.$patch(partial)` 或直接调用 action |
| 字段级 `watch(store.count, cb)` | `store.$subscribe(cb)` |
| 自定义 reset action | `store.$reset()`(`_doReset` 钩子) |
| `loadPersisted` / `savePersisted` + 每字段 ref / watch 胶水 | `createPersistPlugin` + `_serialize` / `_hydrate` 双钩子 |
| `getStoreId(store)` / `dispose(store)` | `store.$id` / `store.$dispose()`(顶层函数仍保留) |
五、快速上手
pnpm add @meng-xi/unix-pinia
mkdir -p uni_modules && cp -R node_modules/@meng-xi/unix-pinia uni_modules/ux-pinia
插件市场搜索 ux-pinia 导入亦可。定义 store 见第二节 class 式示例;页面直接消费 counter.state.count 与 getter。不想搭工程?安卓真机可直接下载体验 App:下载体验 App(APK)——示例工程云打包产物(蒸汽模式),安装后进入各功能页即可在真机运行全部用例。
六、文档与资源
相关链接
- 示例工程 packages/playground(首页分组导航 + 9 个功能验证页,HBuilderX 发行 Web / 云打包验证)
- API 参考
欢迎反馈与共建:GitHub
适合 uni-app x 项目统一状态管理,尤其需要持久化与延续 Pinia 使用方式的团队;注意 0.1.0 为破坏性升级,迁移前先核对 API 对照表。