简介
本技术文档围绕 DouPHP 小程序的状态管理进行系统化说明,覆盖全局状态管理模式(状态树设计、变更流程、订阅机制)、局部状态同步(页面级/组件级/共享方案)、状态持久化策略(序列化、自动保存、恢复)、状态更新最佳实践(不可变数据、合并、版本管理),以及性能优化技巧(分割、按需加载、计算属性)与调试监控方法。文档基于仓库中 miniprogram/company 下的 stores、services、types 等源码进行分析与归纳。
项目结构
小程序端采用 MobX 驱动的全局状态管理,配合统一的 HTTP 层与引导服务,形成“启动即水合、网络再刷新”的 SWR 模式。关键目录与职责:
- stores:全局状态模块(公共数据、登录态、购物车),提供 hydrate/refresh/restore 等生命周期方法
- services:HTTP 封装、引导数据拉取等能力
- types:类型定义,约束 store 字段与 API 信封
- app.ts:应用入口,注册拦截器、启动引导、全局错误处理
graph TB
subgraph "应用入口"
A["app.ts"]
end
subgraph "状态层"
B["stores/index.ts"]
C["stores/common.ts"]
D["stores/auth.ts"]
E["stores/cart.ts"]
F["stores/persist.ts"]
end
subgraph "服务层"
G["services/http.ts"]
H["services/bootstrap.ts"]
end
A --> B
B --> C
B --> D
B --> E
C --> F
C --> H
D --> G
E --> G
H --> G
核心组件
- commonStore:公共数据与特性开关(features),负责种子数据注入、storage 水合、网络刷新并写回持久化
- authStore:登录态与权限标志,负责 token/user_id 持久化、会话恢复、登出与跳转
- cartStore:购物车数量与角标(computed),按 features.order 与登录态决定是否请求
- persist:统一持久化读写,带 schema_version 失效策略
- http:统一 HTTP 层,包含信封解析、拦截器、缓存与去重、调试增强
- bootstrap:引导数据拉取,为 commonStore 提供 site/param/features/nav_list/lang/version 等
架构总览
整体采用“全局 Store + 统一 HTTP + 引导服务”的分层架构:
- 启动阶段:app.onLaunch 注册 HTTP 拦截器,调用 bootstrapStores 完成 hydrate(秒开)→ refresh(网络刷新)→ restore(会话恢复)→ cart 预热
- 数据流:HTTP 层统一信封解析与错误处理;stores 通过 action 修改可观察状态;autorun/computed 驱动 UI 与副作用(如 tabBar 角标)
- 持久化:commonStore 将引导数据写入 storage,schema_version 控制升级失效;authStore 将登录凭证落盘;cartStore 仅内存计数
sequenceDiagram
participant App as "app.ts"
participant Stores as "stores/index.ts"
participant Common as "stores/common.ts"
participant Auth as "stores/auth.ts"
participant Cart as "stores/cart.ts"
participant Http as "services/http.ts"
participant Boot as "services/bootstrap.ts"
App->>Http : 注册 onError(UNAUTHORIZED -> logout)
App->>Stores : bootstrapStores()
Stores->>Common : hydrate()
Stores->>Auth : hydrate()
Stores->>Cart : bindCartBadge()
Stores->>Common : refresh()
Common->>Boot : fetchBootstrap()
Boot->>Http : GET /bootstrap
Http-->>Common : BootstrapData
Common->>Common : applyData() + savePersisted()
Stores->>Auth : restore()
Auth->>Http : GET /user (可能失败)
Stores->>Cart : refresh()
Cart->>Http : GET /order/cart_number (可选)
详细组件分析
全局状态树设计与变更流程
- 状态树
- commonStore:site、lang、param、nav_list、features、data、version、ready
- authStore:api_token、user_id、loginEd、is_login、is_vip、is_work、is_distribution
- cartStore:number(badge 为 computed)
- 变更流程
- 启动时先 hydrate 从 storage 恢复,再 refresh 网络刷新并写回持久化
- 登录态在 restore 中根据后端返回设置 flags,UNAUTHORIZED 时清空登录态
- 购物车数量在 refresh 中根据 features.order 与登录态决定是否请求
flowchart TD
Start(["应用启动"]) --> Hydrate["commonStore.hydrate()<br/>authStore.hydrate()"]
Hydrate --> Refresh["commonStore.refresh()"]
Refresh --> Merge["合并种子/本地/网络数据<br/>applyData()"]
Merge --> Persist["savePersisted('common', data)"]
Merge --> Restore["authStore.restore()"]
Restore --> CartRefresh{"features.order === true ?"}
CartRefresh -- 否 --> CartZero["cartStore.number = 0"]
CartRefresh -- 是 --> CartReq["GET /order/cart_number"]
CartReq --> UpdateCart["cartStore.number = n"]
UpdateCart --> End(["就绪"])
CartZero --> End
状态订阅机制与副作用
- autorun 订阅 badge 变化,驱动 wx.setTabBarBadge/removeTabBarBadge
- computed badge:当 number > 99 显示 '99+',否则显示数字或空串
- 登录态变化由 HTTP 拦截器触发全局登出,避免多处重复处理
classDiagram
class CartStore {
+number : number
+badge : string
+refresh() : Promise<void>
+reset() : void
}
class CommonStore {
+features : Features
+ready : boolean
+hydrate() : void
+refresh() : Promise<void>
}
class AuthStore {
+api_token : string
+user_id : string
+loginEd : boolean
+is_login : boolean
+restore() : Promise<void>
+login(payload) : void
+logout() : void
}
CartStore --> CommonStore : "读取 features.order"
AuthStore --> CommonStore : "读取 features.user"
局部状态同步方案
- 页面级状态:建议以 createStoreBindings 绑定 store 字段到页面 data,实现单向数据流;复杂表单可在页面内维护临时状态,提交后通过 store 动作更新全局
- 组件级状态:复用 store 中的 computed(如 cartStore.badge)减少冗余;组件内部仅保留 UI 交互所需的最小状态
- 状态共享:跨页面共享通过 stores/index.ts 暴露的实例(commonStore/authStore/cartStore),避免多实例导致不一致
状态持久化策略
- 通用持久化:commonStore 将引导数据以 envelope(含 __v)写入 storage,读取时校验版本,不匹配则丢弃旧缓存
- 登录态持久化:authStore 将 api_token、user_id、loginEd 落盘,logout 时清理
- 自动保存:commonStore.refresh 成功后调用 savePersisted;可使用 persistAutorun 对任意 selector 做增量写回
- 恢复机制:启动时先 hydrate 再 refresh,确保首屏秒开且后续一致
flowchart TD
Save["commonStore.refresh() 成功"] --> Envelope["构造 {__v, data}"]
Envelope --> Write["wx.setStorageSync('store:common', JSON)"]
Read["commonStore.hydrate()"] --> Load["wx.getStorageSync('store:common')"]
Load --> Check{"__v 匹配?"}
Check -- 否 --> Ignore["忽略旧缓存"]
Check -- 是 --> Apply["applyData(data)"]
状态更新最佳实践
- 不可变数据:通过 action 包裹状态变更,避免直接突变;HTTP 响应数据经 parseEnvelope 标准化后再写入
- 状态合并:commonStore.applyData 使用 Object.assign 合并 site/lang/features 等,保证缺省值兜底
- 版本管理:persist.ts 使用 SCHEMA_VERSION 控制缓存失效;后端 version 字段用于前端缓存校验提示
性能优化技巧
- 状态分割:按业务域拆分 store(common/auth/cart),降低耦合与重渲染范围
- 按需加载:cartStore.refresh 在未启用 order 或未登录时短路,避免无效请求
- 计算属性:cartStore.badge 作为 computed,避免重复计算与多余 setData
- 请求去重与缓存:http.request 支持 dedupe 与 memory cache,减少并发与重复请求
状态调试与监控工具
- 全局错误钩子:App.onError 输出堆栈,调试环境弹窗;onUnhandledRejection 记录未捕获拒绝
- HTTP 调试:ApiError 携带 request_id,调试期拼接至 message;服务端异常载荷弹出 modal 并支持复制
- 最近请求追踪:getLastRequestId 获取最近一次请求 ID,便于日志对照
- 开发辅助:非正式版开启 vConsole 浮层,提升调试效率
依赖关系分析
- 模块耦合
- index.ts 聚合导出各 store,并编排启动流程
- commonStore 依赖 services/bootstrap 与 services/http,同时被 authStore 与 cartStore 通过 features 降级
- authStore 与 cartStore 均依赖 http 进行网络请求
- persist 提供通用持久化能力,被 commonStore 使用
- 外部依赖
- 微信小程序 API:wx.* 存储、窗口信息、tabBar 角标、更新管理等
- MobX:observable/action/autorun/runInAction/toJS
graph LR
Index["stores/index.ts"] --> Common["stores/common.ts"]
Index --> Auth["stores/auth.ts"]
Index --> Cart["stores/cart.ts"]
Common --> Persist["stores/persist.ts"]
Common --> Bootstrap["services/bootstrap.ts"]
Auth --> Http["services/http.ts"]
Cart --> Http
Bootstrap --> Http
性能考虑
- 首屏体验:hydrate 优先,保障零等待;refresh 异步刷新,失败不影响展示
- 请求优化:GET 去重与内存缓存,减少网络压力;按需刷新(revalidate)控制一致性
- 渲染优化:computed 派生状态减少重复计算;action 批量更新减少多次 setData
- 降级策略:features 控制模块启停,未启用模块短路逻辑,避免无效请求与状态污染
故障排查指南
- 登录态异常
- 现象:频繁弹登录页或状态抖动
- 排查:检查 authStore.restore 的错误分支,确认 UNAUTHORIZED 才清空 is_login;其他错误保留当前态
- 购物车角标不更新
- 现象:tabBar 角标不变化
- 排查:确认 bindCartBadge 已执行;检查 resolveTabIndex 返回值;确认 cartStore.number 是否被正确更新
- 引导数据未生效
- 现象:features/lang/site 未更新
- 排查:查看 commonStore.refresh 是否成功;检查 savePersisted 是否写入;确认 schema_version 是否匹配
- 网络错误定位
- 现象:接口报错但无上下文
- 排查:使用 getLastRequestId 获取最近请求 ID;调试期查看 ApiError.message 中的 req 尾缀;必要时复制服务端异常堆栈
结论
DouPHP 小程序的状态管理以 MobX 为核心,结合统一的 HTTP 层与引导服务,实现了高可用、高性能、易维护的全局状态体系。通过 SWR 模式、模块化 store、持久化版本管理与完善的调试工具链,既保障了首屏体验,又提供了清晰的变更追踪与问题定位能力。建议在新增功能时遵循现有模式:按领域拆分 store、使用 action 更新状态、利用 computed 派生视图、通过 features 控制模块行为,并结合 http 的缓存与去重优化网络开销。
附录
- 类型参考:store.d.ts 定义了 CommonState/AuthState/CartState,确保字段名与 wxml 绑定一致
- 启动流程参考:app.ts 中 onLaunch 的拦截器注册与 bootstrapStores 调用顺序
- 持久化参考:persist.ts 的 envelope 结构与版本失效策略