Files
kunpolibrary/.cursor/rules/data-binding.mdc
2025-11-27 14:49:43 +08:00

347 lines
9.1 KiB
Plaintext

---
description: "强类型数据绑定系统开发规范"
globs: ["src/data/**/*.ts", "**/*Data*.ts"]
alwaysApply: false
type: "data-architecture"
---
# 强类型数据绑定系统规范
## 数据基类设计
### DataBase 基类模式
```typescript
export class DataBase {
/** 数据变化监听器 */
private _watchers: Map<string, Function[]> = new Map();
/**
* 注册属性监听器
* @param path 属性路径
* @param callback 回调函数
*/
public watch(path: string, callback: Function): void {
if (!this._watchers.has(path)) {
this._watchers.set(path, []);
}
this._watchers.get(path)!.push(callback);
}
/**
* 触发属性变化通知
* @param path 属性路径
* @param value 新值
*/
protected notify(path: string, value: any): void {
if (this._watchers.has(path)) {
this._watchers.get(path)!.forEach(callback => {
callback(value);
});
}
}
}
```
### 数据类定义规范
```typescript
class GameData extends DataBase {
private _level: number = 1;
private _coins: number = 0;
private _items: Item[] = [];
// 使用 getter/setter 实现响应式
get level(): number {
return this._level;
}
set level(value: number) {
if (this._level !== value) {
this._level = value;
this.notify('level', value);
}
}
get coins(): number {
return this._coins;
}
set coins(value: number) {
if (this._coins !== value) {
this._coins = value;
this.notify('coins', value);
}
}
// 复杂属性的变化通知
addItem(item: Item): void {
this._items.push(item);
this.notify('items', this._items);
this.notify('items.length', this._items.length);
}
}
```
## 装饰器绑定系统
### 强类型属性绑定
```typescript
export namespace data {
/**
* 强类型属性绑定装饰器
* @param dataClass 数据类构造函数
* @param selector 类型安全的路径选择器
* @param callback 变化回调函数
* @param immediate 是否立即触发
*/
export function bindProp<T extends DataBase>(
dataClass: new () => T,
selector: (data: T) => any,
callback: (item: any, value?: any, data?: T) => void,
immediate: boolean = false
) {
return function (target: any, prop: string | symbol) {
const path = `${dataClass.name}:${extractPathFromSelector(selector)}`;
let ctor = target.constructor;
ctor[BIND_METADATA_KEY] = ctor[BIND_METADATA_KEY] || [];
ctor[BIND_METADATA_KEY].push({
prop, callback, path, immediate, isMethod: false
});
};
}
}
```
### 方法绑定装饰器
```typescript
/**
* 强类型方法绑定装饰器
* @param dataClass 数据类构造函数
* @param selector 类型安全的路径选择器
* @param immediate 是否立即触发
*/
export function bindMethod<T extends DataBase>(
dataClass: new () => T,
selector: (data: T) => any,
immediate: boolean = false
) {
return function (target: any, method: string | symbol, descriptor?: PropertyDescriptor) {
const path = `${dataClass.name}:${extractPathFromSelector(selector)}`;
let ctor = target.constructor;
ctor[BIND_METADATA_KEY] = ctor[BIND_METADATA_KEY] || [];
ctor[BIND_METADATA_KEY].push({
prop: method,
callback: descriptor!.value,
path,
immediate,
isMethod: true
});
return descriptor;
};
}
```
## 使用示例
### UI 数据绑定示例
```typescript
class PlayerData extends DataBase {
name: string = "";
level: number = 1;
exp: number = 0;
maxExp: number = 100;
// 计算属性
get expProgress(): number {
return this.exp / this.maxExp;
}
}
@uiclass("main", "player", "PlayerPanel")
export class PlayerPanel extends Window {
@uiprop nameLabel: GLabel;
@uiprop levelLabel: GLabel;
@uiprop expBar: GProgressBar;
// 绑定玩家名称到标签
@data.bindProp(PlayerData, data => data.name, function(item, value) {
this.nameLabel.text = value;
})
private _nameBinding: any;
// 绑定等级显示
@data.bindMethod(PlayerData, data => data.level)
private onLevelChanged(value: number): void {
this.levelLabel.text = `Lv.${value}`;
}
// 绑定经验条
@data.bindMethod(PlayerData, data => data.expProgress)
private onExpChanged(progress: number): void {
this.expBar.value = progress * 100;
}
protected onInit(): void {
// 初始化绑定
data.initializeBindings(this);
}
protected onClose(): void {
// 清理绑定
data.cleanupBindings(this);
}
}
```
## 绑定管理器
### BindManager 设计
```typescript
export class BindManager {
private static _bindings: Map<string, BindInfo[]> = new Map();
/**
* 添加绑定信息
*/
public static addBinding(bindInfo: BindInfo): void {
const key = bindInfo.path;
if (!this._bindings.has(key)) {
this._bindings.set(key, []);
}
this._bindings.get(key)!.push(bindInfo);
// 如果需要立即触发
if (bindInfo.immediate) {
this.triggerBinding(bindInfo);
}
}
/**
* 清理目标对象的绑定
*/
public static cleanup(target: any): void {
this._bindings.forEach((bindings, path) => {
const newBindings = bindings.filter(binding => binding.target !== target);
if (newBindings.length === 0) {
this._bindings.delete(path);
} else {
this._bindings.set(path, newBindings);
}
});
}
/**
* 触发路径对应的所有绑定
*/
public static notifyChange(path: string, value: any, data: any): void {
const bindings = this._bindings.get(path);
if (bindings) {
bindings.forEach(binding => {
try {
if (binding.isMethod) {
binding.callback.call(binding.target, value, data);
} else {
binding.callback.call(binding.target, binding.target[binding.prop], value, data);
}
} catch (error) {
console.error(`绑定回调执行错误: ${path}`, error);
}
});
}
}
}
```
## 路径解析器
### 路径提取函数
```typescript
/**
* 从选择器函数中提取路径字符串
* 支持编译期类型检查的运行时路径解析
*/
function extractPathFromSelector(selector: Function): string {
const fnString = selector.toString();
// 匹配箭头函数: data => data.property.path
let match = fnString.match(/\w+\s*=>\s*\w+\.(.+)/);
if (!match) {
// 匹配普通函数: function(data) { return data.property.path; }
match = fnString.match(/return\s+\w+\.(.+);?\s*}/);
}
if (!match) {
throw new Error('无效的路径选择器函数,请使用 data => data.property.path 格式');
}
return match[1].trim();
}
```
## 批量更新优化
### BatchUpdater 设计
```typescript
export class BatchUpdater {
private static _pendingUpdates: Set<string> = new Set();
private static _updateTimer: number = 0;
/**
* 批量更新通知
* @param path 变化路径
* @param value 新值
* @param data 数据对象
*/
public static scheduleUpdate(path: string, value: any, data: any): void {
this._pendingUpdates.add(path);
if (this._updateTimer === 0) {
this._updateTimer = requestAnimationFrame(() => {
this.flushUpdates();
});
}
}
/**
* 执行批量更新
*/
private static flushUpdates(): void {
this._pendingUpdates.forEach(path => {
// 获取最新值并触发更新
const [dataClassName, propertyPath] = path.split(':');
const dataInstance = DataRegistry.getInstance(dataClassName);
if (dataInstance) {
const value = this.getValueByPath(dataInstance, propertyPath);
BindManager.notifyChange(path, value, dataInstance);
}
});
this._pendingUpdates.clear();
this._updateTimer = 0;
}
}
```
## 数据绑定最佳实践
### 1. 类型安全
- 使用泛型约束确保绑定的类型安全
- 路径选择器函数提供编译期检查
- 避免字符串路径,优先使用选择器函数
### 2. 性能优化
- 使用批量更新减少频繁触发
- 及时清理不需要的绑定
- 避免在绑定回调中进行重度计算
### 3. 内存管理
- 在组件销毁时调用 `data.cleanupBindings()`
- 避免循环引用导致的内存泄漏
- 使用弱引用管理临时绑定
### 4. 调试支持
- 提供绑定信息的调试接口
- 记录绑定的创建和销毁日志
- 支持绑定状态的实时监控