From 79f1240e1d8c33f011c37f42cd943b42a8a40d61 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 23 Mar 2026 19:35:01 +0800 Subject: [PATCH] =?UTF-8?q?docs(core):=20=E6=B7=BB=E5=8A=A0=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86=E6=96=87=E6=A1=A3=E5=B9=B6=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E5=B1=9E=E6=80=A7=E7=BB=91=E5=AE=9A=E6=8C=87=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 state-management 文档,介绍集中式状态容器方案 - 在 property 文档中补充与 Store 的使用边界说明 - 更新核心功能表格,添加状态管理条目链接 - 在 README 中增加 StateManagement 功能描述 - 添加状态管理相关接口到抽象层文档 - 提供 Store 与 BindableProperty 的选择指导原则 --- GFramework.Core.Abstractions/README.md | 1 + GFramework.Core/README.md | 1 + docs/zh-CN/core/index.md | 41 +++---- docs/zh-CN/core/property.md | 69 ++++++++--- docs/zh-CN/core/state-management.md | 153 +++++++++++++++++++++++++ 5 files changed, 232 insertions(+), 33 deletions(-) create mode 100644 docs/zh-CN/core/state-management.md diff --git a/GFramework.Core.Abstractions/README.md b/GFramework.Core.Abstractions/README.md index f0f4ad4..efd51df 100644 --- a/GFramework.Core.Abstractions/README.md +++ b/GFramework.Core.Abstractions/README.md @@ -12,6 +12,7 @@ GFramework 框架的抽象层定义模块,包含所有核心组件的接口定 - 事件系统接口 (IEvent, IEventBus) - 依赖注入容器接口 (IIocContainer) - 可绑定属性接口 (IBindableProperty) +- 状态管理接口 (IStore, IReducer, IStateSelector) - 日志系统接口 (ILogger) ## 设计原则 diff --git a/GFramework.Core/README.md b/GFramework.Core/README.md index 71339f4..47fc0aa 100644 --- a/GFramework.Core/README.md +++ b/GFramework.Core/README.md @@ -13,6 +13,7 @@ GFramework 框架的核心模块,提供MVC架构的基础设施。 - **Events** - 事件系统,实现组件间松耦合通信 - **IoC** - 轻量级依赖注入容器 - **Property** - 可绑定属性,支持数据绑定和响应式编程 +- **StateManagement** - 集中式状态容器,支持状态归约、选择器和诊断 - **Utility** - 无状态工具类 - **Pool** - 对象池系统,减少GC压力 - **Extensions** - 框架扩展方法 diff --git a/docs/zh-CN/core/index.md b/docs/zh-CN/core/index.md index 4e8618b..a61cfb3 100644 --- a/docs/zh-CN/core/index.md +++ b/docs/zh-CN/core/index.md @@ -105,7 +105,7 @@ Any → FailedInitialization - 初始化/销毁 - Utility 注册 ``` -这种设计遵循单一职责原则,使代码更易维护和测试。 +这种设计遵循单一职责原则,使代码更易维护和测试。 ``` ┌──────────────────┐ @@ -398,30 +398,31 @@ public class PlayerController : IController 4. **易于扩展**: 添加新功能更容易 5. **代码安全**: 消除了 `null!` 断言,所有字段在构造后立即可用 -详细的设计决策已在架构实现重构中落地。 +详细的设计决策已在架构实现重构中落地。 --- ## 包说明 -| 包名 | 职责 | 文档 | -|------------------|-----------------|----------------------| -| **architecture** | 架构核心,管理所有组件生命周期 | [查看](./architecture) | -| **constants** | 框架常量定义 | 本文档 | -| **model** | 数据模型层,存储状态 | [查看](./model) | -| **system** | 业务逻辑层,处理业务规则 | [查看](./system) | -| **controller** | 控制器层,连接视图和逻辑 | (在 Abstractions 中) | -| **utility** | 工具类层,提供无状态工具 | [查看](./utility) | -| **command** | 命令模式,封装写操作 | [查看](./command) | -| **query** | 查询模式,封装读操作 | [查看](./query) | -| **events** | 事件系统,组件间通信 | [查看](./events) | -| **property** | 可绑定属性,响应式编程 | [查看](./property) | -| **ioc** | IoC 容器,依赖注入 | [查看](./ioc) | -| **rule** | 规则接口,定义组件约束 | [查看](./rule) | -| **extensions** | 扩展方法,简化 API 调用 | [查看](./extensions) | -| **logging** | 日志系统,记录运行日志 | [查看](./logging) | -| **environment** | 环境接口,提供运行环境信息 | [查看](./environment) | -| **localization** | 本地化系统,多语言支持 | [查看](./localization) | +| 包名 | 职责 | 文档 | +|----------------------|-----------------|--------------------------| +| **architecture** | 架构核心,管理所有组件生命周期 | [查看](./architecture) | +| **constants** | 框架常量定义 | 本文档 | +| **model** | 数据模型层,存储状态 | [查看](./model) | +| **system** | 业务逻辑层,处理业务规则 | [查看](./system) | +| **controller** | 控制器层,连接视图和逻辑 | (在 Abstractions 中) | +| **utility** | 工具类层,提供无状态工具 | [查看](./utility) | +| **command** | 命令模式,封装写操作 | [查看](./command) | +| **query** | 查询模式,封装读操作 | [查看](./query) | +| **events** | 事件系统,组件间通信 | [查看](./events) | +| **property** | 可绑定属性,响应式编程 | [查看](./property) | +| **state-management** | 集中式状态容器与选择器 | [查看](./state-management) | +| **ioc** | IoC 容器,依赖注入 | [查看](./ioc) | +| **rule** | 规则接口,定义组件约束 | [查看](./rule) | +| **extensions** | 扩展方法,简化 API 调用 | [查看](./extensions) | +| **logging** | 日志系统,记录运行日志 | [查看](./logging) | +| **environment** | 环境接口,提供运行环境信息 | [查看](./environment) | +| **localization** | 本地化系统,多语言支持 | [查看](./localization) | ## 组件联动 diff --git a/docs/zh-CN/core/property.md b/docs/zh-CN/core/property.md index fc16fc1..52070a3 100644 --- a/docs/zh-CN/core/property.md +++ b/docs/zh-CN/core/property.md @@ -2,9 +2,13 @@ ## 概述 -Property 包提供了可绑定属性(BindableProperty)的实现,支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。 - -BindableProperty 是 GFramework 中 Model 层数据管理的基础,通过事件机制实现属性变化的通知。 +Property 包提供了可绑定属性(BindableProperty)的实现,支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。 + +BindableProperty 是 GFramework 中 Model 层数据管理的基础,通过事件机制实现属性变化的通知。 + +> 对于简单字段和局部 UI 绑定,`BindableProperty` 仍然是首选方案。 +> 如果你需要统一管理复杂状态树、通过 action / reducer 演进状态,或复用局部状态选择器, +> 请同时参考 [`state-management`](./state-management)。 ## 核心接口 @@ -136,7 +140,44 @@ BindableProperty 基于事件系统实现属性变化通知: 3. **事件触发**:如果值发生变化,调用所有注册的回调函数 4. **内存管理**:通过 `IUnRegister` 机制管理监听器的生命周期 -## 在 Model 中使用 +## 在 Model 中使用 + +### 什么时候继续使用 BindableProperty + +以下场景仍然优先推荐 `BindableProperty`: + +- 单个字段变化就能驱动视图更新 +- 状态范围局限在单个 Model 内 +- 不需要统一的 action / reducer 写入入口 +- 不需要从聚合状态树中复用局部选择逻辑 + +如果你的状态已经演化为“多个字段必须一起更新”或“多个模块共享同一聚合状态”, +可以在 Model 内部组合 `Store`,而不是把所有字段都继续拆成独立属性。 + +### 与 Store / StateMachine 的边界 + +- `BindableProperty`:字段级响应式值 +- `Store`:聚合状态容器,负责统一归约状态变化 +- `StateMachine`:流程状态切换,不负责数据状态归约 + +一个复杂 Model 可以同时持有 Store 和 BindableProperty: + +```csharp +public class PlayerStateModel : AbstractModel +{ + public Store Store { get; } = new(new PlayerState(100, "Player")); + public BindableProperty IsDirty { get; } = new(false); + + protected override void OnInit() + { + Store.RegisterReducer((state, action) => + state with { Health = Math.Max(0, state.Health - action.Amount) }); + } +} + +public sealed record PlayerState(int Health, string Name); +public sealed record DamageAction(int Amount); +``` ### 定义可绑定属性 @@ -416,18 +457,20 @@ _mOnValueChanged?.Invoke(value); ## 最佳实践 -1. **在 Model 中定义属性** - BindableProperty 主要用于 Model 层 -2. **使用只读接口暴露** - 防止外部随意修改 -3. **及时注销监听** - 使用 UnRegisterList 或 UnRegisterWhenNodeExitTree -4. **使用 RegisterWithInitValue** - UI 绑定时立即获取初始值 -5. **避免循环依赖** - 属性监听器中修改其他属性要小心 -6. **使用自定义比较器** - 对于浮点数等需要精度控制的属性 +1. **在 Model 中定义属性** - BindableProperty 主要用于 Model 层 +2. **使用只读接口暴露** - 防止外部随意修改 +3. **及时注销监听** - 使用 UnRegisterList 或 UnRegisterWhenNodeExitTree +4. **使用 RegisterWithInitValue** - UI 绑定时立即获取初始值 +5. **避免循环依赖** - 属性监听器中修改其他属性要小心 +6. **使用自定义比较器** - 对于浮点数等需要精度控制的属性 +7. **复杂聚合状态使用 Store** - 当多个字段必须统一演进时,使用 Store 管理聚合状态更清晰 ## 相关包 -- [`model`](./model.md) - Model 中大量使用 BindableProperty -- [`events`](./events.md) - BindableProperty 基于事件系统实现 -- [`extensions`](./extensions.md) - 提供便捷的注销扩展方法 +- [`model`](./model.md) - Model 中大量使用 BindableProperty +- [`events`](./events.md) - BindableProperty 基于事件系统实现 +- [`state-management`](./state-management) - 复杂状态树的集中式管理方案 +- [`extensions`](./extensions.md) - 提供便捷的注销扩展方法 --- diff --git a/docs/zh-CN/core/state-management.md b/docs/zh-CN/core/state-management.md new file mode 100644 index 0000000..b659095 --- /dev/null +++ b/docs/zh-CN/core/state-management.md @@ -0,0 +1,153 @@ +# State Management 包使用说明 + +## 概述 + +State Management 提供一个可选的集中式状态容器方案,用于补足 `BindableProperty` 在复杂状态树场景下的能力。 + +当你的状态具有以下特征时,推荐使用 `Store`: + +- 多个字段需要在一次业务操作中协同更新 +- 多个模块或 UI 片段共享同一聚合状态 +- 希望所有状态写入都经过统一的 action / reducer 入口 +- 需要对整棵状态树做局部选择和按片段订阅 + +这套能力不会替代现有 Property 机制,而是与其并存: + +- `BindableProperty`:字段级响应式值 +- `Store`:聚合状态容器 +- `StateMachine`:流程状态切换 + +## 核心接口 + +### IReadonlyStore`` + +只读状态容器接口,提供: + +- `State`:读取当前状态快照 +- `Subscribe()`:订阅状态变化 +- `SubscribeWithInitValue()`:订阅并立即回放当前状态 +- `UnSubscribe()`:取消订阅 + +### IStore`` + +在只读能力上增加: + +- `Dispatch()`:统一分发 action + +### IReducer`` + +定义状态归约逻辑: + +```csharp +public interface IReducer +{ + TState Reduce(TState currentState, TAction action); +} +``` + +### IStateSelector`` + +从整棵状态树中投影局部视图,便于 UI 和 Controller 复用选择逻辑。 + +## Store`` + +`Store` 是默认实现,支持: + +- 初始状态快照 +- reducer 注册 +- middleware 分发管线 +- 只在状态真正变化时通知订阅者 +- 基础诊断信息(最近一次 action、最近一次分发记录、最近一次状态变化时间) + +## 基本示例 + +```csharp +using GFramework.Core.StateManagement; + +public sealed record PlayerState(int Health, string Name); +public sealed record DamageAction(int Amount); +public sealed record RenameAction(string Name); + +var store = new Store(new PlayerState(100, "Player")) + .RegisterReducer((state, action) => + state with { Health = Math.Max(0, state.Health - action.Amount) }) + .RegisterReducer((state, action) => + state with { Name = action.Name }); + +store.SubscribeWithInitValue(state => +{ + Console.WriteLine($"{state.Name}: {state.Health}"); +}); + +store.Dispatch(new DamageAction(25)); +store.Dispatch(new RenameAction("Knight")); +``` + +## 选择器和 Bindable 风格桥接 + +Store 可以通过扩展方法把聚合状态投影成局部只读绑定视图: + +```csharp +using GFramework.Core.Extensions; + +var healthSelection = store.Select(state => state.Health); + +healthSelection.RegisterWithInitValue(health => +{ + Console.WriteLine($"Current HP: {health}"); +}); +``` + +如果现有 UI 代码已经依赖 `IReadonlyBindableProperty`,可以直接桥接: + +```csharp +IReadonlyBindableProperty healthProperty = + store.ToBindableProperty(state => state.Health); +``` + +## 在 Model 中使用 + +推荐把 Store 作为 Model 的内部状态容器,由 Model 暴露领域友好的业务方法: + +```csharp +public class PlayerStateModel : AbstractModel +{ + public Store Store { get; } = new(new PlayerState(100, "Player")); + + protected override void OnInit() + { + Store.RegisterReducer((state, action) => + state with { Health = Math.Max(0, state.Health - action.Amount) }); + } + + public void TakeDamage(int amount) + { + Store.Dispatch(new DamageAction(amount)); + } +} +``` + +这样可以保留 Model 的生命周期和领域边界,同时获得统一状态入口。 + +## 什么时候不用 Store + +以下情况继续优先使用 `BindableProperty`: + +- 单一字段直接绑定 UI +- 状态规模很小,不需要聚合归约 +- 没有跨模块共享状态树的需求 +- 你只需要“值变化通知”,不需要“统一状态演进入口” + +## 最佳实践 + +1. 优先把 `TState` 设计为不可变状态(如 `record`) +2. 让 reducer 保持纯函数风格,不在 reducer 内执行副作用 +3. 使用 selector 暴露局部状态,而不是让 UI 自己解析整棵状态树 +4. 需要日志或诊断时,优先通过 middleware 扩展,而不是把横切逻辑塞进 reducer + +## 相关文档 + +- [`property`](./property) - 字段级响应式属性 +- [`model`](./model) - Store 常见承载位置 +- [`events`](./events) - 组件间事件通信 +- [`state-machine-tutorial`](../tutorials/state-machine-tutorial) - 流程状态切换能力