跳转至

响应式状态与控件绑定

GFReactiveStateStore 提供运行时 Dictionary 状态树、路径级变更记录、dirty queue 和 owner 自动解绑。它适合项目界面、调试面板、设置页或编辑工具中同时维护多个字段状态,但不定义字段含义,也不替代业务 Model。

定位

  • GFBindableProperty:Model 暴露单个字段,Controller 直接订阅。
  • GFReactiveEffect / GFComputedProperty:组合多个 GFBindableProperty 形成局部副作用或派生值。
  • GFFormBinder:批量读写一组表单控件,不持有应用状态。
  • GFReactiveStateStore:维护一棵通用状态树,按路径生成变更记录并批量派发。
  • GFReactiveStateControlBinder:把一个 Control 的值和 store path 双向同步。

如果项目已有清晰的业务 Model,应优先把业务状态归属留在 Model 中;store 更适合 UI 本地状态、工具状态、筛选条件、面板草稿和可丢弃的运行时视图状态。

状态路径

路径可以使用点号字符串、StringNamePackedStringArrayArray

var store := GFReactiveStateStore.new({
    "profile": {
        "name": "Ada",
    },
})

store.set_value("profile.name", "Grace")
print(store.get_value(["profile", "name"]))

String 路径按 . 分段,Array 路径可包含 int 段读取已有数组索引。自动创建中间容器时只创建 Dictionary,不会自动扩容数组。

Dirty Queue

普通写入会立即 flush:

store.subscribe("profile.name", func(change: Dictionary, _store: GFReactiveStateStore) -> void:
    %NameLabel.text = GFVariantData.to_text(change["new_value"])
)

store.set_value("profile.name", "Grace")

需要合并多次写入时使用批量模式:

store.begin_batch()
store.set_value("profile.name", "Grace")
store.set_value("profile.level", 2)
var changes := store.end_batch()

同一路径在一个批次内会合并为一条 dirty change,并保留第一次写入前的旧值和最后一次写入后的新值。set_state() 会复用 GFVariantData.diff_variant() 生成路径级差异,方便调试、日志和保存工具复用同一种变更记录形态。

订阅回调、state_changedpath_changed 中再次写入 store 时,新 dirty change 不会重入当前正在通知的订阅者批次;当前批次完成后,store 会按下一批继续派发,避免嵌套 flush 打乱同一批订阅者顺序。

订阅与解绑

subscribe() 返回取消订阅的 Callableoptions.owner 传入 Node 时,该节点退出场景树会自动取消订阅:

var unsubscribe := store.subscribe("filters", _on_filters_changed, {
    "mode": GFReactiveStateStore.SUBSCRIBE_PREFIX,
    "owner": self,
    "emit_current": true,
})

options.owner 也可以是普通 Object / RefCounted。这类 owner 没有场景树退出信号,store 会通过 WeakRefflush()get_subscription_count() 或后续清理点懒移除失效订阅。

订阅模式:

  • SUBSCRIBE_EXACT:只接收完全相同路径。
  • SUBSCRIBE_PREFIX:接收该路径及其子路径。
  • SUBSCRIBE_ANY:接收所有路径。

回调签名固定为 func(change: Dictionary, store: GFReactiveStateStore) -> void。变更记录包含 kindpathpath_segmentsold_valuenew_valueold_existsnew_existsold_typenew_type

控件绑定

GFReactiveStateControlBinder 复用 GFControlValueAdapter,支持 LineEditTextEditCheckBoxSliderOptionButtonColorPickerButtonItemList 和提供 value / get_value() / set_value() 的自定义控件:

var store := GFReactiveStateStore.new({
    "profile": {
        "name": "Ada",
    },
})
var binder := GFReactiveStateControlBinder.new()

binder.bind_control(store, "profile.name", %NameEdit)

默认行为是先把 store 中的值写入控件,再监听控件变化写回 store。需要用控件当前值初始化 store 时:

binder.bind_control(store, "profile.name", %NameEdit, {
    "write_initial_to_store": true,
})

控件退出场景树、store 被释放或显式调用 unbind_control() / clear() 时,binder 会断开控件信号,并通过 store 的 owner 订阅清理路径监听。

使用边界

  • Store 只保存通用 Variant 数据,不解释字段语义、权限、校验规则或业务动作。
  • Store 不替代 GFArchitecture 中的 Model/System/Utility 生命周期。
  • 需要字段类型声明和默认值校验时,使用 GFBlackboardSchemaGFDictionarySchema 处理数据契约,再把结果写入 store。
  • 需要提交/回滚一组业务操作时,使用 GFMutationBatch;store 的 batch 只合并通知,不提供事务语义。

参考测试:

  • tests/gf_core/standard/utilities/state/test_gf_reactive_state_store.gd
  • tests/gf_core/standard/utilities/ui/test_gf_reactive_state_control_binder.gd