简介
本技术文档聚焦 DouPHP 小程序前端的“stores”状态管理方案,围绕认证、购物车与全局公共数据三大领域,系统阐述状态定义、更新、订阅与响应式更新的完整链路。重点解释:
- 认证状态 authStore:登录态持久化、恢复、降级策略与鉴权流程。
- 购物车状态 cartStore:数量聚合、角标计算与模块开关控制。
- 全局协调 index.ts:启动引导、统一绑定与副作用驱动。
- 公共数据 commonStore:种子数据、网络刷新与本地缓存的 SWR 模式。
- 持久化 persist.ts:带 schema 版本的存储封装与自动写回。
- 响应式原理:基于 MobX 的可观察对象、动作与 autorun 驱动的视图同步与性能优化。
项目结构
小程序前端的状态管理集中在 miniprogram/default/stores 目录,采用模块化 store 设计,通过统一的入口进行初始化与副作用绑定。类型定义位于 types/store.d.ts,MobX 运行时由 libs/mobx-miniprogram 提供。
graph TB
subgraph "stores"
A["auth.ts<br/>认证状态"]
B["cart.ts<br/>购物车状态"]
C["common.ts<br/>公共数据"]
D["index.ts<br/>启动与绑定"]
E["persist.ts<br/>持久化封装"]
end
subgraph "types"
T["store.d.ts<br/>状态类型声明"]
end
subgraph "libs"
M["mobx-miniprogram/index.js<br/>可观察/动作/autorun"]
end
D --> A
D --> B
D --> C
C --> E
A --> M
B --> M
C --> M
A -.-> T
B -.-> T
C -.-> T
核心组件
- 认证状态(authStore):负责登录凭证、用户标识与登录态标志的持久化、恢复与校验;支持可选模块降级。
- 购物车状态(cartStore):维护购物车数量与角标文案;在未启用订单模块或未登录时短路返回零值。
- 公共数据(commonStore):承载站点信息、语言包、导航、功能特性等;采用 SWR 模式实现首屏秒开与后台刷新。
- 启动协调(bootstrapStores):按序执行 hydrate → refresh/restore,并绑定购物车角标的 autorun 副作用。
- 持久化(persist.ts):以 schema_version 保护缓存版本,提供 load/save/clear 与 autorun 自动写回能力。
架构总览
整体采用“单一真相源 + 可观察状态 + 副作用驱动”的模式:
- 数据层:commonStore 作为 bootstrap 信封的唯一真相源,authStore 与 cartStore 分别维护各自域的状态。
- 状态更新:所有变更通过 action 包裹,确保在 MobX 事务中批量提交,避免中间态闪烁。
- 响应式更新:使用 autorun/computed 将状态变化映射到 UI 或平台 API(如 tabBar 角标)。
- 启动流程:先 hydrate 从 storage 快速渲染,再并发/串行刷新网络数据,失败不阻断首屏。
sequenceDiagram
participant App as "应用启动"
participant Bootstrap as "bootstrapStores"
participant Common as "commonStore"
participant Auth as "authStore"
participant Cart as "cartStore"
participant UI as "视图/TabBar"
App->>Bootstrap : 调用
Bootstrap->>Common : hydrate()
Bootstrap->>Auth : hydrate()
Bootstrap->>UI : bindCartBadge() (autorun)
Bootstrap->>Common : refresh()
Common-->>Bootstrap : 完成(含 features)
Bootstrap->>Auth : restore()
Bootstrap->>Cart : refresh()
Note over Auth,Cart : 根据 features 与登录态决定是否请求
Cart-->>UI : badge 变化触发 set/remove TabBarBadge
详细组件分析
认证状态(authStore)
- 状态字段:api_token、user_id、loginEd、is_login、is_vip、is_work、is_distribution。
- 关键方法:
- hydrate:从 storage 读取凭证与登录标记,用于冷启动秒开。
- restore:根据 commonStore.features.user 决定是否拉取 user/index;仅 UNAUTHORIZED 时清空 is_login,其它错误保留当前态,避免弱网假登出。
- ensureLogin:若未登录则跳转默认登录页。
- login/logout:写入/清除 storage 并同步状态。
- 降级策略:当 features.user === false 时,强制 is_login 为 false,且不发起任何请求。
flowchart TD
Start(["进入 restore"]) --> CheckModule{"features.user 是否启用?"}
CheckModule --> |否| SetEmpty["applyAuthFlags({}) 置空标志"] --> End(["结束"])
CheckModule --> |是| Fetch["调用 user/index"]
Fetch --> Resp{"响应码"}
Resp --> |UNAUTHORIZED| Clear["applyAuthFlags({}) 清空标志"] --> End
Resp --> |其他/成功| Apply["applyAuthFlags(后端 flags)"] --> End
购物车状态(cartStore)
- 状态字段:number(数量)。
- 派生属性:badge(角标文案),0 显示空串,>99 显示 '99+'。
- 关键方法:
- refresh:在未启用 order 模块或未登录时短路返回 0;否则请求 order/cart_number 并更新 number。
- reset:重置数量为 0。
- 降级策略:order 模块未启用或未登录时,静默返回 0,不抛错、不弹提示。
flowchart TD
S(["refresh"]) --> Mod{"order 模块已启用?"}
Mod --> |否| ZeroA["number=0"] --> DoneA(["返回"])
Mod --> |是| Token{"存在 api_token?"}
Token --> |否| ZeroB["number=0"] --> DoneB(["返回"])
Token --> |是| Call["请求 order/cart_number"]
Call --> Update["number = 返回值(默认0)"] --> DoneC(["返回"])
全局协调(index.ts)
- 职责:导出各 store;绑定购物车角标的 autorun;提供 bootstrapStores 启动流程。
- 启动顺序:
- commonStore.hydrate() 与 authStore.hydrate() 立即填充本地缓存数据。
- bindCartBadge() 建立 autorun,监听 cartStore.badge 变化并调用 wx.setTabBarBadge / removeTabBarBadge。
- commonStore.refresh() 完成后,再调用 authStore.restore() 与 cartStore.refresh(),依据最新 features 决定降级行为。
sequenceDiagram
participant Entry as "入口"
participant BS as "bootstrapStores"
participant CM as "commonStore"
participant AU as "authStore"
participant CT as "cartStore"
participant WX as "wx API"
Entry->>BS : 调用
BS->>CM : hydrate()
BS->>AU : hydrate()
BS->>BS : bindCartBadge()
BS->>CM : refresh()
CM-->>BS : 完成
BS->>AU : restore()
BS->>CT : refresh()
CT-->>WX : set/remove TabBarBadge
公共数据(commonStore)
- 作用:承载站点配置、语言包、导航、功能特性等;对外暴露 features,供 module-guard、authStore、cartStore 做降级判断。
- 生命周期:
- applyData:合并种子数据与网络数据,设置 ready=true。
- hydrate:从持久化加载上次刷新结果,保证首屏可用。
- refresh:并行获取 bootstrap 与 lang,合并后写回持久化;失败不影响已有数据。
flowchart TD
H["hydrate"] --> Load["loadPersisted('common')"]
Load --> ApplyH{"有缓存?"}
ApplyH --> |是| UseCache["applyData(缓存)"] --> ReadyH["ready=true"]
ApplyH --> |否| SkipH["跳过"] --> ReadyH
R["refresh"] --> Fetch["fetchBootstrap + fetchLang"]
Fetch --> Merge["合并 payload 与 langPack"]
Merge --> ApplyR["applyData(合并结果)"]
ApplyR --> Save["savePersisted('common', merged)"]
持久化(persist.ts)
- 设计要点:
- 以 { __v: SCHEMA_VERSION, data } 结构存储,版本升级自动失效旧缓存。
- 提供 load/save/clear 工具函数,异常静默处理。
- 提供 persistAutorun:用 autorun 订阅 selector,变化即写回 storage。
classDiagram
class Persist {
+SCHEMA_VERSION : number
+loadPersisted(key) : any
+savePersisted(key, data) : void
+clearPersisted(key) : void
+persistAutorun(key, selector) : () => void
}
响应式与类型
- 响应式基础:observable/action/runInAction/autorun 来自 mobx-miniprogram,提供可观察对象、受控修改与副作用订阅。
- 类型契约:store.d.ts 定义了 CommonState/AuthState/CartState,确保 store 字段名与页面 wxml 绑定一致,形成单一真相源。
依赖关系分析
- 模块内聚:每个 store 专注单一业务域,通过 commonStore.features 解耦可选模块。
- 外部依赖:
- http:用于网络请求(user/index、order/cart_number)。
- route:动态解析路由键,未注册路由时抛出 Unknown route,被上层 try/catch 捕获并降级。
- wx API:storage、redirectTo、set/removeTabBarBadge。
- 耦合点:
- index.ts 对 cartStore.badge 的 autorun 绑定是跨模块副作用的关键点。
- authStore 与 cartStore 均依赖 commonStore.features 做降级决策。
graph LR
Index["index.ts"] --> Auth["auth.ts"]
Index --> Cart["cart.ts"]
Index --> Common["common.ts"]
Common --> Persist["persist.ts"]
Auth --> Http["services/http.js"]
Cart --> Http
Auth --> Route["utils/route.js"]
Cart --> Route
Index --> Wx["wx API"]
性能考虑
- 首屏秒开:commonStore.hydrate 与 authStore.hydrate 优先从 storage 恢复,减少白屏时间。
- 网络刷新后置:在 hydrate 之后再进行 refresh/restore,失败不阻断首屏渲染。
- 局部更新:action 包裹状态变更,配合 MobX 细粒度追踪,仅更新依赖该状态的视图节点。
- 角标绑定去抖:autorun 仅在 badge 变化时调用 wx API,避免频繁 set/remove。
- 降级短路:order 未启用或未登录时直接返回 0,避免无效请求。
故障排查指南
- 登录后仍显示未登录:
- 检查 ensureLogin 与 restore 的 UNAUTHORIZED 分支是否正确清空 is_login。
- 确认 http 拦截器是否正确携带 Authorization: Bearer 并处理会话过期。
- 参考路径:miniprogram/default/stores/auth.ts:91-115
- 购物车角标不更新:
- 确认 bindCartBadge 已执行且 resolveTabIndex 能正确解析购物车 tab 索引。
- 检查 cartStore.refresh 是否因 features.order=false 或未登录而短路。
- 参考路径:miniprogram/default/stores/index.ts:20-42、miniprogram/default/stores/cart.ts:54-72
- 首屏数据为空:
- 检查 commonStore.hydrate 是否能从 persist 加载上次缓存。
- 确认 refresh 失败时不会覆盖已有数据。
- 参考路径:miniprogram/default/stores/common.ts:74-106
- 版本升级后缓存失效:
- 确认 persist.ts 的 SCHEMA_VERSION 已递增,旧缓存将被丢弃。
- 参考路径:miniprogram/default/stores/persist.ts:9-34
结论
DouPHP 小程序的状态管理以 MobX 为核心,采用“可观察状态 + 动作 + 副作用”的清晰分层:
- commonStore 提供统一的数据真相源与模块开关;
- authStore 与 cartStore 分别管理认证与购物车域,具备完善的降级与容错;
- index.ts 集中编排启动流程与 UI 副作用;
- persist.ts 保障缓存版本安全与自动写回。 该方案在保证首屏体验的同时,提供了可扩展、可维护的状态管理基础设施。
附录
- 最佳实践
- 始终通过 action 修改状态,避免绕过 MobX 追踪。
- 使用 computed/autorun 表达派生状态与副作用,保持逻辑与视图解耦。
- 利用 features 控制可选模块,确保未安装模块时的稳定降级。
- 对网络请求失败保持幂等与静默兜底,避免影响用户体验。
- 使用 persist 的版本机制管理数据结构演进。
- 常见问题
- 路由未注册导致 route() 抛错:在上层 try/catch 中捕获并降级。
- 非 tabBar 页面调用 setTabBarBadge 失败:在 catch 中静默忽略。
- 弱网导致误判未登录:仅在 UNAUTHORIZED 时清空 is_login。