mirror of
https://github.com/Gongxh0901/kunpolibrary
synced 2025-12-05 20:29:10 +00:00
添加项目规则;修复几处潜在问题
This commit is contained in:
347
.cursor/rules/data-binding.mdc
Normal file
347
.cursor/rules/data-binding.mdc
Normal file
@@ -0,0 +1,347 @@
|
||||
---
|
||||
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. 调试支持
|
||||
- 提供绑定信息的调试接口
|
||||
- 记录绑定的创建和销毁日志
|
||||
- 支持绑定状态的实时监控
|
||||
Reference in New Issue
Block a user