文档目录
组件状态管理

简介

本技术文档围绕 DouPHP 小程序的组件状态管理,系统化阐述:

  • 组件内部状态管理:数据绑定、响应式更新、状态同步机制
  • 组件间通信模式:父子组件(props/事件回调)、兄弟组件(事件总线)、跨层级(全局状态)
  • 全局状态管理方案:状态存储、订阅、更新策略
  • 异步状态处理:数据加载、缓存策略、错误处理
  • 状态持久化:本地存储、云端同步、数据迁移
  • 完整示例:复杂业务场景下的状态流转与处理逻辑

项目结构

小程序端采用“多 Store + 统一 HTTP 层 + 启动引导”的分层设计。核心目录与职责如下:

  • stores:按领域拆分 store(common/auth/cart),提供可观察状态与变更方法
  • services:HTTP 封装、应用引导数据拉取等
  • types:Store 与 API 类型声明,保证字段一致性
  • app.ts:应用入口,注册拦截器、启动引导、全局错误处理
  • default/company:两套小程序模板共享相同的状态管理模式
graph TB
subgraph "小程序应用"
A["app.ts"]
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
D --> G
E --> G
C --> H

图表来源

  • app.ts:12-35
  • stores/index.ts(公司版):1-61
  • stores/common.ts:1-106
  • stores/auth.ts:1-156
  • stores/cart.ts:1-78
  • services/http.ts:1-403
  • services/bootstrap.ts:1-13

章节来源

  • app.ts:12-35
  • stores/index.ts(公司版):1-61

核心组件

  • commonStore:公共数据与模块开关(features),负责种子数据水合、网络刷新、持久化写回
  • authStore:登录态与权限标志,负责 token 持久化、会话恢复、登出与跳转
  • cartStore:购物车数量与角标,负责模块降级、未登录短路、网络获取数量
  • http:统一请求封装,信封解析、拦截器、去重、内存缓存、调试增强
  • bootstrap:应用引导数据拉取,驱动 features 与站点信息
  • persist:带 schema 版本的持久化读写,支持自动写回

章节来源

  • stores/common.ts:1-106
  • stores/auth.ts:1-156
  • stores/cart.ts:1-78
  • services/http.ts:1-403
  • services/bootstrap.ts:1-13
  • stores/persist.ts:1-60

架构总览

小程序启动时,app.ts 注册 HTTP 错误拦截器并调用 bootstrapStores(),后者依次执行:

  • hydrate:从 storage 秒开 common/auth 数据
  • refresh:网络刷新 common,再触发 auth.restore 与 cart.refresh
  • autorun:cart.badge 变化驱动 tabBar 角标
sequenceDiagram
participant App as "app.ts"
participant Boot 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 Bootstrap as "services/bootstrap.ts"
App->>Boot : bootstrapStores()
Boot->>Common : hydrate()
Boot->>Auth : hydrate()
Boot->>Cart : bindCartBadge() (autorun)
Boot->>Common : refresh()
Common->>Bootstrap : fetchBootstrap()
Bootstrap->>Http : GET /bootstrap
Http-->>Common : {site, features, ...}
Common-->>Boot : ready=true
Boot->>Auth : restore()
Auth->>Http : GET /user/index
Http-->>Auth : {dou.auth}
Boot->>Cart : refresh()
Cart->>Http : GET /order/cart_number
Http-->>Cart : number
Cart-->>App : badge 变化 -> setTabBarBadge

图表来源

  • app.ts:23-35
  • stores/index.ts(公司版):20-61
  • stores/common.ts:74-106
  • services/bootstrap.ts:9-12
  • services/http.ts:194-339
  • stores/auth.ts:91-115
  • stores/cart.ts:54-72

详细组件分析

公共数据 Store(commonStore)

  • 作用:承载站点信息、语言包、路由参数、导航列表、模块开关(features)、版本与就绪标记
  • 关键流程:
    • applyData:合并后端返回与种子数据,设置 ready
    • hydrate:从持久化读取上次刷新结果,实现秒开
    • refresh:并行拉取 bootstrap 与语言包,失败保留已有数据;成功后写回持久化
  • 模块开关:features 由后端下发并与默认值合并,供 auth/cart 做降级判断
flowchart TD
Start(["refresh 入口"]) --> FetchB["fetchBootstrap()"]
FetchB --> FetchLang["fetchLang()"]
FetchLang --> Merge{"语言包为空?"}
Merge --> |是| UseExisting["使用现有语言包"]
Merge --> |否| UseNew["使用新语言包"]
UseExisting --> Apply["applyData(合并后数据)"]
UseNew --> Apply
Apply --> Persist["savePersisted('common', data)"]
Persist --> End(["完成"])

图表来源

  • stores/common.ts:81-106

章节来源

  • stores/common.ts:1-106

登录态 Store(authStore)

  • 作用:维护 api_token、user_id、loginEd 及 is_login/is_vip/is_work/is_distribution 等标志
  • 关键流程:
    • hydrate:从 storage 恢复凭证与登录标记
    • restore:根据 features.user 决定是否请求 user/index;UNAUTHORIZED 时清空登录态
    • ensureLogin:未登录则跳转到登录页
    • login/logout:写入/清除 storage 并更新状态
  • 降级策略:当 features.user === false 时,直接清空标志位,不发请求
flowchart TD
S(["restore 入口"]) --> CheckMod{"features.user 是否禁用?"}
CheckMod --> |是| ClearFlags["清空 flags"]
CheckMod --> |否| FetchUser["GET /user/index"]
FetchUser --> Ok{"code===OK?"}
Ok --> |是| ApplyFlags["applyAuthFlags(dou.auth)"]
Ok --> |否| HandleErr{"code==='UNAUTHORIZED'?"}
HandleErr --> |是| ClearFlags
HandleErr --> |否| KeepState["保持当前状态"]
ClearFlags --> End(["结束"])
ApplyFlags --> End
KeepState --> End

图表来源

  • stores/auth.ts:64-115

章节来源

  • stores/auth.ts:1-156

购物车 Store(cartStore)

  • 作用:维护购物车数量 number 与只读计算属性 badge(用于 tabBar 角标)
  • 关键流程:
    • refresh:若 order 模块未启用或未登录,直接置 0;否则请求 order/cart_number
    • reset:重置数量为 0
  • 降级策略:order 模块未启用或无 token 时短路,避免无效请求
flowchart TD
S(["refresh 入口"]) --> CheckOrder{"order 模块启用?"}
CheckOrder --> |否| SetZero["number=0"]
CheckOrder --> |是| CheckToken{"存在 api_token?"}
CheckToken --> |否| SetZero
CheckToken --> |是| FetchNum["GET /order/cart_number"]
FetchNum --> Update["number=返回数量"]
SetZero --> End(["结束"])
Update --> End

图表来源

  • stores/cart.ts:21-72

章节来源

  • stores/cart.ts:1-78

统一 HTTP 层(http)

  • 职责:
    • 信封解析:code 为 OK 视为成功,否则抛出 ApiError
    • 默认头注入:Content-Type 与 Authorization: Bearer <api_token>
    • 拦截器:onRequest/onSuccess/onError,支持 UNAUTHORIZED 统一登出
    • 去重与缓存:相同 GET 在飞行中复用 Promise;可选内存缓存与 TTL
    • 调试:记录 request_id,调试环境弹出服务端异常堆栈
  • 对外接口:request/get/post/put/del/clearCache/getLastRequestId
classDiagram
class Http {
+request(config, opts) Promise
+get(url, data, opts) Promise
+post(url, data, opts) Promise
+put(url, data, opts) Promise
+del(url, data, opts) Promise
+clearCache(prefix) void
+getLastRequestId() string
+onRequest(fn) void
+onSuccess(fn) void
+onError(fn) void
}
class ApiError {
+code string
+statusCode number
+errors object
+data object
+request_id string
}
Http --> ApiError : "构造/抛出"

图表来源

  • services/http.ts:40-65
  • services/http.ts:194-339
  • services/http.ts:381-403

章节来源

  • services/http.ts:1-403

应用引导(bootstrap)

  • 作用:拉取站点配置、功能开关、导航与语言版本等,作为 commonStore 的单一真相源
  • 调用时机:commonStore.refresh 中优先拉取,失败不影响首屏

章节来源

  • services/bootstrap.ts:1-13
  • stores/common.ts:81-106

持久化(persist)

  • 作用:以 schema_version 包裹数据,版本不匹配即失效;支持 autorun 自动写回
  • 关键点:load/save/clear 均静默容错;persistAutorun 将 selector 结果持久化

章节来源

  • stores/persist.ts:1-60

页面与组件中的状态绑定与响应式更新

  • 绑定方式:通过 createStoreBindings 将 store 字段映射到 wxml,确保「store 字段名 <-> wxml 绑定名」一致
  • 响应式更新:基于 MobX observable/action/computed/autorun,状态变更后自动触发视图更新
  • 典型用法:
    • 表单输入:action 更新 store,wxml 双向绑定
    • 列表渲染:computed 派生展示数据,减少重复计算
    • 副作用:autorun 监听状态变化执行 UI 侧操作(如 tabBar 角标)

章节来源

  • types/store.d.ts:1-50
  • stores/index.ts(公司版):20-42

依赖关系分析

  • 启动链路:app.ts -> stores/index.ts -> commonStore.hydrate/refresh -> authStore.restore -> cartStore.refresh
  • 数据流向:http -> services/bootstrap -> commonStore.applyData -> persist.savePersisted
  • 鉴权链路:http.onError -> authStore.logout -> 清理 storage 与状态
  • 角标联动:cartStore.badge 计算 -> autorun -> wx.setTabBarBadge
graph LR
App["app.ts"] --> Boot["stores/index.ts"]
Boot --> Common["stores/common.ts"]
Boot --> Auth["stores/auth.ts"]
Boot --> Cart["stores/cart.ts"]
Common --> Persist["stores/persist.ts"]
Auth --> Http["services/http.ts"]
Cart --> Http
Common --> Bootstrap["services/bootstrap.ts"]
Http --> |错误| App

图表来源

  • app.ts:23-35
  • stores/index.ts(公司版):20-61
  • stores/common.ts:74-106
  • stores/auth.ts:91-115
  • stores/cart.ts:54-72
  • services/http.ts:23-29

章节来源

  • stores/index.ts(公司版):1-61
  • stores/common.ts:1-106
  • stores/auth.ts:1-156
  • stores/cart.ts:1-78
  • services/http.ts:1-403

性能考量

  • 冷启动优化:先 hydrate 再 refresh,storage 秒开,网络失败不影响首屏
  • 请求去重:相同 GET 在飞行中复用同一 Promise,降低并发压力
  • 内存缓存:GET 请求可配置 cache/ttl,命中直接返回
  • 模块降级:features 控制可选模块,未启用时短路请求,减少不必要 IO
  • 角标联动:autorun 仅绑定一次,避免重复 setTabBarBadge
  • 调试开销:仅在调试环境开启 vConsole 与异常弹窗,生产零额外负担

故障排查指南

  • 统一错误处理:
    • HTTP 层将非 OK 信封与网络异常统一包装为 ApiError,包含 code/message/errors/request_id
    • 全局 onError 捕获未处理异常,调试环境弹窗提示
    • onPageNotFound 捕获路由错误,便于定位拼写问题
  • 常见错误码与处理:
    • UNAUTHORIZED:触发 authStore.logout,清理登录态
    • NETWORK_ERROR:保留当前状态,避免弱网误判
    • HTTP_5xx:保留当前状态,避免频繁弹框
  • 调试技巧:
    • 使用 getLastRequestId 关联最近一次请求
    • 调试环境弹出服务端异常堆栈,复制便于定位
    • 利用 console.error 输出请求方法与 URL

章节来源

  • services/http.ts:150-188
  • services/http.ts:234-339
  • app.ts:69-96

结论

DouPHP 小程序采用基于 MobX 的全局状态管理,结合统一的 HTTP 层与启动引导,实现了:

  • 清晰的数据流:种子 -> 持久化 -> 网络刷新 -> 视图更新
  • 健壮的降级:模块开关与未登录短路,保障体验
  • 高效的交互:autorun 驱动 UI 副作用,减少冗余代码
  • 完善的排障:统一错误模型与调试钩子,提升定位效率

该方案适用于复杂业务场景,具备良好的可扩展性与可维护性。

附录

组件间通信模式与实践

  • 父子组件通信:
    • props 传递:父组件通过属性向子组件传递只读数据
    • 事件回调:子组件通过事件向上汇报用户操作,父组件更新 store
  • 兄弟组件通信:
    • 事件总线:通过全局事件中心发布/订阅,解耦组件耦合
    • 共享 store:兄弟组件共同订阅同一 store 字段,实现状态同步
  • 跨层级通信:
    • 全局状态:commonStore/authStore/cartStore 作为单一真相源,任意层级均可访问
    • 副作用集中:autorun/computed 集中在 store 层,避免分散的副作用

异步状态处理最佳实践

  • 数据加载:
    • 使用 refresh/hydrate 分离冷启动与网络刷新
    • 对 GET 请求启用 cache/revalidate 控制 SWR 行为
  • 缓存策略:
    • 内存缓存:适合短时效热点数据
    • 持久化缓存:适合站点配置、语言包等低频变更数据
  • 错误处理:
    • 区分网络错误与业务错误,采取不同降级策略
    • 对 UNAUTHORIZED 进行幂等登出,避免重复跳转

状态持久化方案

  • 本地存储:
    • 使用 persist 模块进行结构化持久化,带 schema 版本控制
    • 通过 autorun 自动写回,保证状态与存储一致
  • 云端同步:
    • 通过 http.post/put/del 提交变更,服务端落库
    • 失败时保留本地状态,重试或提示用户
  • 数据迁移:
    • 升级 SCHEMA_VERSION 使旧缓存失效,强制重新拉取
    • 兼容旧数据结构,逐步迁移至新格式

完整示例:订单页加入购物车后的状态流转

  • 用户点击“加入购物车”
    • 调用 http.post 提交商品 ID 与数量
    • 成功后触发 cartStore.refresh 获取最新数量
    • cartStore.badge 变化,autorun 更新 tabBar 角标
  • 若未登录或 order 模块未启用
    • 直接短路,number 置 0,不发起请求
  • 若网络异常
    • 保留当前 number,不中断用户操作
sequenceDiagram
participant Page as "订单页"
participant Http as "services/http.ts"
participant Cart as "stores/cart.ts"
participant Badge as "tabBar 角标"
Page->>Http : POST /order/add_to_cart
Http-->>Page : {success}
Page->>Cart : refresh()
Cart->>Http : GET /order/cart_number
Http-->>Cart : number
Cart-->>Badge : badge 变化 -> setTabBarBadge

图表来源

  • stores/cart.ts:54-72
  • services/http.ts:341-364
添加日期:2026-10-05