mirror of
https://github.com/Gongxh0901/kunpolibrary
synced 2025-09-13 18:08:43 +00:00
7.4 KiB
7.4 KiB
数据绑定(Data Binding)
本库的数据绑定面向“简单直接”的 UI 同步:当数据类的属性被设置或数据类上的公开方法被调用时,触发与之匹配的装饰器回调,用最少样板代码完成 UI 更新。复杂交互(动画、跨模块协作、节流/并发控制)仍建议使用事件系统或显式逻辑处理。
- 优势
- 精简:无需注册/注销一堆事件监听,几行装饰器即可完成 UI 同步
- 类型安全:通过 selector 函数选择数据路径,配合 TS 编译期检查
- 零侵入:数据类只需继承
DataBase
,代理自动拦截属性设置与方法调用 - 高性能:同一帧内变更合并批量分发(
BatchUpdater
)
- 适用边界
- 简单、直接的属性展示或方法触发的轻量级反馈
- 复杂动画链路、跨系统通信、长时流程控制 → 使用事件系统更明确可控
基本概念
- 数据类:继承
DataBase
。属性赋值和公开方法调用都会发出变更通知 - 绑定路径:
ClassName:memberName
,由装饰器通过 selector 推导 - selector 约束:仅支持
d => d.xxx.yyy
或function(d){ return d.xxx.yyy; }
的直链式访问;不支持动态表达式/可选链/解构 - 触发时机:
- 属性变更:设置新值且与旧值不同才触发(以
_
开头的属性被忽略) - 方法调用:公开方法被调用即触发(
constructor
与以_
开头的方法被忽略)
- 属性变更:设置新值且与旧值不同才触发(以
- 生命周期:
- 初始化:
data.initializeBindings(this)
(FGUI/窗口基类已内置自动调用) - 清理:
data.cleanupBindings(this)
(FGUI 组件dispose
已内置调用)
- 初始化:
- 即时更新:装饰器
immediate
参数,默认 false;为 true 时属性变更会立刻执行一次回调
API 速览
import { data } from "kunpocc"; // 实际从 src/data/DataDecorator 导出
import { DataBase } from "kunpocc";
// 属性绑定(装饰到“UI字段”上)
data.bindProp<T extends DataBase>(
dataClass: new () => T,
selector: (d: T) => any,
callback: (item: any, value?: any, data?: T) => void,
immediate: boolean = false,
)
// 方法绑定(装饰到“UI方法”上)
data.bindMethod<T extends DataBase>(
dataClass: new () => T,
selector: (d: T) => any, // 选择数据类上的某个“公开方法”
immediate: boolean = false,
)
回调参数说明(属性绑定):
item
:被装饰的 UI 字段(例如某Label
)value
:属性新值(仅属性变更时有值)data
:当前数据类实例(便于读取其它字段)
方法绑定回调时机:当目标数据方法被调用后触发,默认不传 value
,你可以在回调内读取 data
的当前状态。
使用方式(从简到难)
1) 属性装饰器(同一窗口类中合并展示“单个/多个装饰器”)
import * as fgui from "fairygui-cc";
import { DataBase, data, Window, _uidecorator } from "kunpocc";
const { uiclass, uiprop } = _uidecorator;
// 示例数据类
class Level extends DataBase {
public levelid = 1;
public storey = 1;
}
// 使用窗口(继承 kunpo.Window),同一类中分别演示:
// A) 单个装饰器 B) 多个装饰器(同一 UI 属性上堆叠多个装饰器)
@uiclass("Window", "Data", "DataWindow")
export class DataWindow extends Window {
// --- A) 单个装饰器:仅响应 storey ---
@uiprop
@data.bindProp(Level, d => d.storey, (item: fgui.GTextField, value: number) => {
item.text = `层数:${value}`;
})
private lab_storey!: fgui.GTextField;
// --- B) 多个装饰器:同一属性上堆叠,分别响应 storey 与 levelid ---
@uiprop
@data.bindProp(Level, d => d.storey, (item: fgui.GTextField, value: number, model: Level) => {
item.text = `关卡:${model.levelid} 层数:${value}`;
})
@data.bindProp(Level, d => d.levelid, (item: fgui.GTextField, value: number, model: Level) => {
item.text = `关卡:${value} 层数:${model.storey}`;
})
private lab_level!: fgui.GTextField;
}
// 触发(示意)
const level = new Level();
level.storey = 2; // 触发 lab_storey 与 lab_level 的第一个装饰器
level.levelid = 10; // 触发 lab_level 的第二个装饰器
要点:
- A/B 两段示例均在“窗口类”中,满足“属性装饰器使用窗口”的要求
- “多个装饰器”强调“同一个 UI 属性”上堆叠多个
@bindProp
,参考 demo 的DataWindow.ts
与DataItem.ts
immediate
可按需加在装饰器末参数,设为 true 时“属性变更会立即执行一次回调”(首帧对齐)
2) 方法装饰器(同一自定义组件中合并展示“单个/多个装饰器”)
import * as fgui from "fairygui-cc";
import { DataBase, data, _uidecorator } from "kunpocc";
const { uicom } = _uidecorator;
// 示例数据类
class Inventory extends DataBase {
public addItem(it: string) { /* ... */ }
public reset() { /* ... */ }
}
// 使用自定义组件(继承 fgui.GComponent),同一类中分别演示:
// A) 单个方法装饰器 B) 多个方法装饰器(同一 UI 方法上堆叠多个装饰器)
@uicom("Data", "DataItem")
export class DataItem extends fgui.GComponent {
// --- A) 单个方法装饰器:仅响应 addItem ---
@data.bindMethod(Inventory, d => d.addItem)
onAddItem(inv: Inventory) {
// 轻量反馈:刷新一次列表/播放提示
// this.refresh(inv);
}
// --- B) 多个方法装饰器:同一 UI 方法上堆叠,响应 reset 与 addItem ---
@data.bindMethod(Inventory, d => d.reset)
@data.bindMethod(Inventory, d => d.addItem)
onAny(inv: Inventory) {
// 统一刷新
// this.refresh(inv);
}
}
// 触发(示意)
const inv = new Inventory();
inv.addItem("Potion"); // 触发 onAddItem 与 onAny
inv.reset(); // 仅触发 onAny
要点:
- 示例放在“自定义组件”中,满足“方法装饰器使用自定义组件”的要求
- “多个装饰器”强调“同一个 UI 方法”上堆叠多个
@bindMethod
,参考 demo 的DataWindow.ts
(refreshData
) - 方法绑定不关心入参与返回值,调用即触发;复杂动画仍建议事件系统编排
生命周期与集成
- FGUI 自定义组件与窗口基类已内置:
- 构造(onConstruct)时:
data.initializeBindings(this)
dispose()
时:data.cleanupBindings(this)
- 构造(onConstruct)时:
- 非集成场景(如普通类):
- 初始化后手动调用
data.initializeBindings(this)
- 销毁前调用
data.cleanupBindings(this)
- 初始化后手动调用
参考代码位置:
src/ui/ComponentExtendHelper.ts
内在构造与dispose
中已自动处理src/fgui/WindowBase.ts
同样在生命周期中调用了初始化
最佳实践与注意事项
- 关注点内聚:属性绑定仅做“值到视图”的即时映射;复杂动作用事件系统
- 避免回调里递归触发同一数据的写操作,防止无意义的级联更新
- 以
_
或__
开头的属性/方法不会触发绑定,不要用作绑定目标 selector
必须是直链式访问:d => d.foo.bar
,不要写表达式/调用/条件语句immediate
用于是否立即触发更新,默认关闭以避免不必要开销
调试小贴士
import { BindManager } from "kunpocc";
BindManager.getAllPaths(); // 查看已注册的所有路径
BindManager.getTotalBindingCount(); // 总绑定数量
如果绑定无效:检查数据类是否继承了 DataBase
、selector
是否为直链式、是否在生命周期内完成了 initializeBindings
。