文档目录
状态管理

简介

本技术文档围绕 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 结构与版本失效策略
添加日期:2026-10-05