企业应用中的状态管理架构:从全局到局部的分层设计

引言

在星办 OA 这个企业级办公审批应用中,状态管理是架构设计的核心挑战之一。应用需要处理审批流程的状态流转、消息通知的已读/未读状态、用户信息的展示,以及多个页面之间的数据共享和同步。HarmonyOS NEXT 提供了丰富的状态管理机制,从全局的 AppStorageV2 到组件级别的 @Local,形成了一个完整的分层状态管理体系。

本文将深入分析星办 OA 项目的整体状态管理架构,探讨从全局状态到组件状态的分层设计、单向数据流模式,以及如何通过 ApprovalStore 实现集中式状态管理。

一、状态管理架构总览

1.1 三层状态模型

星办 OA 的状态管理架构可以分为三个层次:

┌─────────────────────────────────────────────┐
│  第一层:全局状态(AppStorageV2)             │
│  - ApprovalStore(审批数据)                  │
│  - pageInfos(导航栈)                        │
│  - topRectHeight / bottomRectHeight(设备参数)│
├─────────────────────────────────────────────┤
│  第二层:页面状态(@Local)                   │
│  - tabCurrentIndex(Tab 选中索引)            │
│  - selectedView(审批视图筛选)               │
│  - selectedType(审批类型筛选)               │
│  - keyword(搜索关键词)                      │
├─────────────────────────────────────────────┤
│  第三层:组件状态(@Local / @Param)           │
│  - comment(审批意见输入)                    │
│  - title/summary/reason(表单输入)           │
│  - approvalNotificationEnabled(开关状态)     │
└─────────────────────────────────────────────┘

这种分层设计遵循了以下原则:

  • 全局共享的数据存储在 AppStorageV2
  • 页面相关的临时状态使用 @Local 管理
  • 组件内部状态使用 @Local@Param 接收父组件数据

1.2 核心状态管理类:ApprovalStore

ApprovalStore 是整个应用的状态中心,它集中管理了所有审批相关的数据:

// commons/common/src/main/ets/model/ApprovalStore.ets
@ObservedV2
export class ApprovalStore {
  @Type(ApprovalRequest)
  @Trace approvals: ApprovalRequest[] = createDemoApprovals()

  @Type(ApprovalMessage)
  @Trace messages: ApprovalMessage[] = createDemoMessages()

  @Trace profile: EmployeeProfile = new EmployeeProfile()
}

ApprovalStore 的设计体现了以下特性:

  • 单例模式:通过 AppStorageV2.connect 确保全局只有一个实例
  • 集中管理:所有审批数据、消息数据和用户信息集中在一个 Store 中
  • 响应式:使用 @ObservedV2@Trace 实现响应式数据绑定
  • 类型安全:使用 @Type 标注复杂类型,确保运行时类型一致性

二、全局状态管理:AppStorageV2

2.1 AppStorageV2 连接模式

在星办 OA 项目中,每个页面组件都通过 AppStorageV2.connect 连接到全局的 ApprovalStore 实例:

// HomePage.ets
@Local store: ApprovalStore = AppStorageV2.connect<ApprovalStore>(
  ApprovalStore, 
  () => new ApprovalStore()
)!

// OfficePage.ets
@Local store: ApprovalStore = AppStorageV2.connect<ApprovalStore>(
  ApprovalStore, 
  () => new ApprovalStore()
)!

// InteractionPage.ets
@Local store: ApprovalStore = AppStorageV2.connect<ApprovalStore>(
  ApprovalStore, 
  () => new ApprovalStore()
)!

// MinePage.ets
@Local store: ApprovalStore = AppStorageV2.connect<ApprovalStore>(
  ApprovalStore, 
  () => new ApprovalStore()
)!

这种连接模式的特性:

  • 延迟初始化() => new ApprovalStore() 是一个工厂函数,只在首次连接时调用
  • 自动同步:所有连接的组件共享同一个实例,数据自动同步
  • 类型安全:泛型参数 ApprovalStore 确保连接的类型正确
  • 非空断言! 断言连接一定成功

2.2 全局导航状态

除了数据状态,导航状态也通过全局机制管理。MainPage 使用 @Provider 提供导航栈:

// MainPage.ets
@Provider('pageInfos') pageInfos: NavPathStack = new NavPathStack()

其他页面通过 @Consumer 消费该导航栈:

// HomePage.ets
@Consumer('pageInfos') pageInfos: NavPathStack = new NavPathStack()

// OfficePage.ets
@Consumer('pageInfos') pageInfos: NavPathStack = new NavPathStack()

这种 @Provider/@Consumer 模式实现了跨组件的数据共享,而不需要将数据层层传递。

2.3 设备参数全局存储

应用还使用了 AppStorage(非 V2 版本)来存储设备参数:

// 在页面中使用
.padding({ top: Number(AppStorage.get('topRectHeight')) })

这些参数在应用启动时初始化,用于适配不同设备的屏幕区域(如状态栏高度、底部导航栏高度)。

三、页面状态管理:@Local

3.1 @Local 的作用域

@Local 装饰器用于管理页面级别的状态,这些状态只在当前页面及其子组件中可见。与全局状态不同,@Local 状态的生命周期与组件绑定。

HomePage 中:

@ComponentV2
export struct HomePage {
  @Local store: ApprovalStore = AppStorageV2.connect<ApprovalStore>(...)!
  @Consumer('pageInfos') pageInfos: NavPathStack = new NavPathStack()
}

这里 store 虽然是 @Local 属性,但它的值是从全局 Store 连接而来的,因此 store.approvals 等属性的变化会自动同步到所有页面。

3.2 页面筛选状态的本地管理

OfficePage 中,多个筛选状态使用 @Local 管理:

@ComponentV2
export struct OfficePage {
  @Local store: ApprovalStore = AppStorageV2.connect<ApprovalStore>(...)!
  @Local selectedView: string = ApprovalView.PENDING
  @Local selectedType: string = '全部'
  @Local keyword: string = ''
  @Consumer('pageInfos') pageInfos: NavPathStack = new NavPathStack()
}

这些筛选状态是页面私有的,不需要与其他页面共享:

  • selectedView:当前选中的视图(待我审批/我发起的/已处理)
  • selectedType:当前选中的审批类型筛选
  • keyword:搜索关键词
当用户切换视图或修改筛选条件时,这些 @Local 状态发生变化,触发 UI 重新渲染,调用 getVisibleApprovals() 方法获取过滤后的数据。

3.3 表单状态的本地管理

ApprovalCreatePage 中,表单输入状态使用 @Local 管理:

@ComponentV2
export struct ApprovalCreatePage {
  @Param approvalType: string = '请假'
  @Local store: ApprovalStore = AppStorageV2.connect<ApprovalStore>(...)!
  @Local selectedType: string = '请假'
  @Local title: string = ''
  @Local summary: string = ''
  @Local reason: string = ''
}

这些表单状态是典型的临时状态,只在表单填写过程中存在,提交后即被清除。

四、组件状态管理:@Param 与 @Event

4.1 父子组件数据传递

星办 OA 使用 @Param@Event 实现父子组件之间的数据通信。以 CustomTabBar 为例:

// CustomTabBar.ets
@ComponentV2
export struct CustomTabBar {
  @Param currentIndex: number = 0
  @Event $currentIndex: (index: number) => void = () => {}
  // ...
}

父组件 MainPage 使用 CustomTabBar

// MainPage.ets
@Local tabCurrentIndex: number = 0

build() {
  // ...
  CustomTabBar({ currentIndex: this.tabCurrentIndex!! })
  // ...
}

这种设计实现了:

  • 数据向下传递tabCurrentIndex 通过 @Param 传递给子组件
  • 事件向上传递:子组件通过 @Event 通知父组件状态变化

4.2 页面参数传递

对于页面级别的参数传递,星办 OA 使用 NavigationParams

// CommonInterface.ets
export class NavigationParams {
  title: string = ''
  constructor(title: string) {
    this.title = title
  }
}

页面跳转时传递参数:

this.pageInfos.pushPathByName('ApprovalDetail', new NavigationParams(item.id))

目标页面通过 @Param 接收参数:

@ComponentV2
export struct ApprovalDetailPage {
  @Param approvalId: string = ''
  // ...
}

五、单向数据流模式

5.1 数据流方向

星办 OA 遵循单向数据流模式,数据流向始终是:

AppStorageV2 (全局状态) → @Local (页面状态) → @Param (组件状态)

状态的变更只能通过 Store 提供的公共方法进行:

用户操作 → Store 方法 → 状态变更 → UI 更新

5.2 在 ApprovalStore 中的体现

ApprovalStore 的所有公共方法都遵循不可变更新模式,确保数据流是单向且可预测的:

submit(type, title, summary, reason): ApprovalMutation {
  let result = submitApproval(this.approvals, input, id, '刚刚')
  if (result.success) {
    this.approvals = result.approvals  // 不可变更新
    this.addActionMessage(result, '申请已提交', ...)
  }
  return result
}

approve(id, comment): ApprovalMutation {
  let result = approveApproval(this.approvals, id, comment, '刚刚')
  if (result.success) {
    this.approvals = result.approvals  // 不可变更新
    this.messages = resolvePendingMessages(this.messages, id, result.action)
    this.addActionMessage(result, '审批操作已完成', ...)
  }
  return result
}

每个方法都:

  • 接收当前状态作为输入
  • 调用纯函数处理业务逻辑
  • 返回新的状态(不可变更新)
  • 触发响应式更新

5.3 跨页面数据同步

由于所有页面连接到同一个 AppStorageV2 Store,当在一个页面中修改了数据,其他页面会自动同步更新:

  • 用户在 ApprovalDetailPage 中执行审批操作(同意/驳回)
  • ApprovalStore.approve()reject() 方法被调用
  • 状态更新后,HomePage 中的待办列表自动刷新
  • InteractionPage 中的消息列表自动更新
  • MinePage 中的统计数据自动变化

六、状态管理架构的设计原则

6.1 单一数据源

整个应用只有一个 ApprovalStore 实例,所有审批数据都从这个单一数据源获取。这避免了数据不一致的问题。

6.2 状态提升

当多个组件需要共享同一状态时,状态被提升到共同的父组件或全局 Store 中。例如,tabCurrentIndex 被提升到 MainPage,通过 @Provider@Event 传递给子组件。

6.3 状态隔离

页面级别的筛选状态(selectedViewselectedTypekeyword)使用 @Local 管理,不与其他页面共享,实现了状态隔离。

6.4 最小化全局状态

只有真正需要在多个页面间共享的数据才放入 AppStorageV2。临时状态(如表单输入、弹窗状态)保持在本地组件中。

七、实际数据流分析

7.1 审批操作的数据流

以用户在 ApprovalDetailPage 中点击"同意"按钮为例,数据流如下:

  • 用户操作:点击"同意"按钮 → confirmApprove() 被调用
  • 确认弹窗AlertDialog.show() 显示确认对话框
  • 执行操作:用户确认 → this.store.approve(this.approval.id, this.comment) 被调用
  • Store 处理approve() 方法调用 approveApproval() 纯函数
  • 状态更新this.approvals = result.approvals 触发响应式更新
  • 消息同步this.messages = resolvePendingMessages(...) 更新消息状态
  • UI 更新:所有连接到 Store 的组件自动重新渲染
  • 反馈提示promptAction.showToast({ message: result.message }) 显示操作结果

7.2 审批提交的数据流

用户在 ApprovalCreatePage 中提交申请:

  • 表单验证confirmSubmit() 检查必填字段
  • 确认弹窗:显示确认对话框
  • 执行提交this.store.submit(...) 调用 Store 方法
  • 创建审批submitApproval() 创建新的 ApprovalRequest 实例
  • 状态更新this.approvals = [item, ...approvals] 在数组头部插入新元素
  • 页面跳转replacePathByName 跳转到审批详情页
  • 全局同步:首页的待办统计和进行中列表自动更新

八、总结

星办 OA 的状态管理架构体现了企业级应用的最佳实践:

  • 分层设计:全局状态(AppStorageV2)→ 页面状态(@Local)→ 组件状态(@Param),形成了清晰的状态层次
  • 单向数据流:数据从上层向下层传递,事件从下层向上层传播,保证了数据流的可预测性
  • 集中式 Store:通过 ApprovalStore 实现集中式状态管理,所有数据变更通过 Store 的公共方法进行
  • 响应式同步@ObservedV2/@Trace 确保状态变更自动同步到所有 UI 组件
  • 状态隔离:页面级别的临时状态使用 @Local 管理,避免不必要的全局状态污染

这种架构设计使得星办 OA 应用具有良好的可维护性、可扩展性和性能表现,为构建复杂的企业级应用提供了可靠的架构基础。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐