mirror of
https://github.com/Gongxh0901/kunpolibrary
synced 2025-10-30 10:55:40 +00:00
UI模块添加数据绑定装饰器
1.添加数据基类,子类自动添加代理,数据变化自动通知 2.支持同属性多装饰器
This commit is contained in:
125
src/data/BatchUpdater.ts
Normal file
125
src/data/BatchUpdater.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { BindManager } from './BindManager';
|
||||
import { BindInfo, IDataEvent } from './types';
|
||||
|
||||
/**
|
||||
* 挂起更新信息
|
||||
*/
|
||||
interface PendingUpdate {
|
||||
/** 绑定器信息 */
|
||||
info: BindInfo;
|
||||
/** 路径变化事件 */
|
||||
event: IDataEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新调度器
|
||||
* 负责将同一帧内的多次数据变化合并为一次更新,提升性能
|
||||
*/
|
||||
export class BatchUpdater {
|
||||
/** 挂起的更新任务集合 */
|
||||
private static pendingUpdates = new Map<string, PendingUpdate>();
|
||||
/** 是否已调度批量更新 */
|
||||
private static isScheduled = false;
|
||||
/** 立即更新的绑定器集合(防重复触发) */
|
||||
private static immediateUpdates = new Set<string>();
|
||||
|
||||
/**
|
||||
* 通知所有匹配的绑定器
|
||||
* @param event 路径变化事件
|
||||
*/
|
||||
public static notifyBindings(event: IDataEvent): void {
|
||||
const bindInfos = BindManager.getMatchingBindings(event.path);
|
||||
|
||||
for (const info of bindInfos) {
|
||||
if (info.immediate) {
|
||||
// 立即更新模式
|
||||
this.executeImmediateUpdate(info, event);
|
||||
} else {
|
||||
// 批量更新模式
|
||||
this.scheduleBatchUpdate(info, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行立即更新(防止同一帧内重复触发)
|
||||
* @param info 绑定器
|
||||
* @param event 变化事件
|
||||
*/
|
||||
private static executeImmediateUpdate(info: BindInfo, event: IDataEvent): void {
|
||||
const key = this.getBindingKey(info);
|
||||
|
||||
// 防止同一帧内重复执行
|
||||
if (this.immediateUpdates.has(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.immediateUpdates.add(key);
|
||||
|
||||
try {
|
||||
info.callback.call(info.target, event);
|
||||
} catch (error) {
|
||||
console.error(`绑定器回调执行失败,路径:${event.path}`, error);
|
||||
} finally {
|
||||
// 下一帧清理标记
|
||||
setTimeout(() => {
|
||||
this.immediateUpdates.delete(key);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度批量更新
|
||||
* @param info 绑定器
|
||||
* @param event 变化事件
|
||||
*/
|
||||
private static scheduleBatchUpdate(info: BindInfo, event: IDataEvent): void {
|
||||
const key = this.getBindingKey(info);
|
||||
|
||||
// 同一绑定器在一帧内只保留最后一次更新
|
||||
this.pendingUpdates.set(key, { info, event });
|
||||
|
||||
// 如果还未调度,则调度一次批量更新
|
||||
if (!this.isScheduled) {
|
||||
this.isScheduled = true;
|
||||
setTimeout(() => this.flush(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行所有挂起的更新任务
|
||||
*/
|
||||
private static flush(): void {
|
||||
// 先复制当前状态
|
||||
// 清理原始状态
|
||||
// 安全处理复制的数据
|
||||
const updates = Array.from(this.pendingUpdates.values());
|
||||
this.pendingUpdates.clear();
|
||||
this.isScheduled = false;
|
||||
|
||||
for (const { info, event } of updates) {
|
||||
try {
|
||||
let target = info.target;
|
||||
if (info.isMethod) {
|
||||
info.callback.call(target, event.target);
|
||||
} else {
|
||||
info.callback.call(target, target[info.prop], event.isProp ? event.value : undefined, event.target);
|
||||
}
|
||||
} catch (error) {
|
||||
// 单个绑定器异常不影响其他绑定器的执行
|
||||
console.error(`绑定器回调执行失败,路径:${event.path}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成绑定器唯一键
|
||||
* @param binding 绑定器
|
||||
*/
|
||||
private static getBindingKey(info: BindInfo): string {
|
||||
if (info.isMethod) {
|
||||
return `${info.target.__data_id__}:${info.prop.toString()}`;
|
||||
}
|
||||
return `${info.target.__data_id__}:${info.prop.toString()}:${info.path}`;
|
||||
}
|
||||
}
|
||||
90
src/data/BindManager.ts
Normal file
90
src/data/BindManager.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { BindInfo } from './types';
|
||||
|
||||
export class BindManager {
|
||||
/**
|
||||
* 绑定器集合
|
||||
* 键:路径
|
||||
* 值:绑定器集合
|
||||
*/
|
||||
private static _bindings = new Map<string, Set<BindInfo>>();
|
||||
|
||||
static addBinding(info: BindInfo): void {
|
||||
// 延迟初始化:在第一次添加绑定时确保实例已正确初始化
|
||||
this._ensureInstanceInitialized(info.target);
|
||||
|
||||
if (!this._bindings.has(info.path)) {
|
||||
this._bindings.set(info.path, new Set());
|
||||
}
|
||||
this._bindings.get(info.path)!.add(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保实例已正确初始化(延迟初始化策略)
|
||||
* 这样可以适配所有场景:独立使用、@uicom、@uiclass
|
||||
*/
|
||||
private static _ensureInstanceInitialized(instance: any): void {
|
||||
// 如果已经初始化过,直接返回
|
||||
if (instance.__bindings_initialized__) {
|
||||
return;
|
||||
}
|
||||
const ctor = instance.constructor as any;
|
||||
// 生成唯一ID
|
||||
if (!instance.__data_id__) {
|
||||
instance.__data_id__ = `${ctor.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
||||
}
|
||||
|
||||
// 标记已初始化
|
||||
instance.__bindings_initialized__ = true;
|
||||
}
|
||||
|
||||
static removeBinding(info: BindInfo): void {
|
||||
const pathBindings = this._bindings.get(info.path);
|
||||
if (pathBindings) {
|
||||
pathBindings.delete(info);
|
||||
if (pathBindings.size === 0) {
|
||||
this._bindings.delete(info.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static getMatchingBindings(path: string): Set<BindInfo> {
|
||||
// 直接通过路径获取绑定器集合,避免不必要的遍历
|
||||
return this._bindings.get(path) || new Set<BindInfo>();
|
||||
}
|
||||
|
||||
static cleanup(target: any): void {
|
||||
const toRemove: BindInfo[] = [];
|
||||
|
||||
for (const bindingSet of this._bindings.values()) {
|
||||
bindingSet.forEach(binding => {
|
||||
if (binding.target === target) {
|
||||
toRemove.push(binding);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toRemove.forEach(binding => this.removeBinding(binding));
|
||||
}
|
||||
|
||||
static clearAll(): void {
|
||||
this._bindings.clear();
|
||||
}
|
||||
|
||||
/************** 调试用 **************/
|
||||
static getBindingsForPath(path: string): Set<BindInfo> {
|
||||
return this._bindings.get(path) || new Set();
|
||||
}
|
||||
|
||||
static getTotalBindingCount(): number {
|
||||
let count = 0;
|
||||
for (const bindingSet of this._bindings.values()) {
|
||||
count += bindingSet.size;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static getAllPaths(): string[] {
|
||||
return Array.from(this._bindings.keys());
|
||||
}
|
||||
/************** 调试用 **************/
|
||||
}
|
||||
45
src/data/DataBase.ts
Normal file
45
src/data/DataBase.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { BindManager } from './BindManager';
|
||||
import { ProxyObject } from './ProxyHandler';
|
||||
import { BindInfo } from './types';
|
||||
|
||||
/**
|
||||
* 响应式数据基类
|
||||
* 通过 Proxy 拦截属性访问,实现零侵入式响应式数据绑定
|
||||
*/
|
||||
export class DataBase {
|
||||
/** 响应式对象唯一标识 */
|
||||
private __data_id__: string;
|
||||
/** 绑定器集合 */
|
||||
private __watchers__: Set<BindInfo>;
|
||||
/** 是否已销毁 */
|
||||
private __destroyed__: boolean = false;
|
||||
|
||||
constructor() {
|
||||
// 返回包装后的对象,自动使用 constructor.name
|
||||
return ProxyObject(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁响应式对象,清理所有绑定器
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.__destroyed__ = true;
|
||||
this.__watchers__.clear();
|
||||
|
||||
BindManager.cleanup(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应式对象ID
|
||||
*/
|
||||
public getDataId(): string {
|
||||
return this.__data_id__;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已销毁
|
||||
*/
|
||||
public isDestroyed(): boolean {
|
||||
return this.__destroyed__;
|
||||
}
|
||||
}
|
||||
125
src/data/DataDecorator.ts
Normal file
125
src/data/DataDecorator.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { BindManager } from './BindManager';
|
||||
import { DataBase } from './DataBase';
|
||||
import { BindInfo } from './types';
|
||||
|
||||
export namespace data {
|
||||
/**
|
||||
* @bindAPI 装饰器元数据存储键
|
||||
*/
|
||||
const BIND_METADATA_KEY = Symbol('__bind_metadata__');
|
||||
|
||||
/**
|
||||
* 为实例初始化绑定器(在装饰器收集完所有绑定信息后调用)
|
||||
* @param instance 目标实例
|
||||
*/
|
||||
export function initializeBindings(instance: any) {
|
||||
const ctor = instance.constructor as any;
|
||||
const binds = ctor[BIND_METADATA_KEY] || [];
|
||||
|
||||
for (const info of binds) {
|
||||
const bindInfo: BindInfo = {
|
||||
target: instance,
|
||||
prop: info.prop,
|
||||
callback: info.isMethod ? instance[info.prop].bind(instance) : info.callback.bind(instance),
|
||||
path: info.path,
|
||||
immediate: info.immediate,
|
||||
isMethod: info.isMethod
|
||||
};
|
||||
// 注册到全局绑定器管理器(BindManager 会自动处理延迟初始化)
|
||||
BindManager.addBinding(bindInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强类型属性绑定装饰器
|
||||
*
|
||||
* @param dataClass 显示传入数据类,用于获取类名
|
||||
* @param selector 类型安全的路径选择器函数
|
||||
* @param callback.item: 当前装饰的类属性
|
||||
* @param callback.value: 如果绑定的是数据属性,则value为数据属性值,否则为undefined
|
||||
* @param callback.data: 数据类实例
|
||||
* @param immediate 是否立即触发,默认true
|
||||
*/
|
||||
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)}`;
|
||||
// console.log('绑定属性的监听路径', path);
|
||||
|
||||
let ctor = target.constructor;
|
||||
// 存储绑定元数据
|
||||
ctor[BIND_METADATA_KEY] = ctor[BIND_METADATA_KEY] || [];
|
||||
ctor[BIND_METADATA_KEY].push({
|
||||
target: null,
|
||||
prop,
|
||||
callback,
|
||||
path: path,
|
||||
immediate,
|
||||
isMethod: false
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 强类型方法绑定装饰器
|
||||
* 在编译期验证路径有效性,防止重构时出现绑定失效
|
||||
*
|
||||
* @param dataClass 显示传入数据类,用于获取类名
|
||||
* @param selector 类型安全的路径选择器函数
|
||||
* @param immediate 是否立即触发,默认false
|
||||
*/
|
||||
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)}`;
|
||||
// console.log('绑定方法的监听路径', path);
|
||||
// 存储绑定元数据
|
||||
let ctor = target.constructor;
|
||||
ctor[BIND_METADATA_KEY] = ctor[BIND_METADATA_KEY] || [];
|
||||
|
||||
ctor[BIND_METADATA_KEY].push({
|
||||
target: null,
|
||||
prop: method,
|
||||
callback: descriptor!.value,
|
||||
path: path,
|
||||
immediate,
|
||||
isMethod: true
|
||||
});
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从选择器函数中提取路径字符串
|
||||
* 这是运行时路径解析,配合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 或 function(data) { return data.property.path; } 的形式');
|
||||
}
|
||||
|
||||
return match[1].trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动清理目标对象的所有绑定器
|
||||
* @param target 目标对象
|
||||
*/
|
||||
export function cleanupBindings(target: any): void {
|
||||
BindManager.cleanup(target);
|
||||
|
||||
if (target.__watchers__) {
|
||||
target.__watchers__.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
167
src/data/ProxyHandler.ts
Normal file
167
src/data/ProxyHandler.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @Author: Gongxh
|
||||
* @Date: 2025-08-26
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
import { BatchUpdater } from "./BatchUpdater";
|
||||
import { IDataEvent } from "./types";
|
||||
|
||||
// 全局唯一ID生成器
|
||||
let nextId = 1;
|
||||
|
||||
function notifyChange(path: string, dataInstance: any, value?: any, isProp: boolean = false) {
|
||||
const event: IDataEvent = {
|
||||
path,
|
||||
target: dataInstance,
|
||||
value,
|
||||
isProp
|
||||
};
|
||||
// console.log('发出的通知路径', path);
|
||||
BatchUpdater.notifyBindings(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理属性设置的拦截逻辑
|
||||
*/
|
||||
function handlePropertySet(dataInstance: any, target: any, prop: string | symbol, value: any): boolean {
|
||||
let oldValue = Reflect.get(target, prop);
|
||||
if (oldValue === value) {
|
||||
// 数据不变 无需通知 无需修改
|
||||
return true;
|
||||
}
|
||||
|
||||
let propname = prop.toString();
|
||||
// 排除以_和__开头的方法
|
||||
if (propname.startsWith('_')) {
|
||||
Reflect.set(target, prop, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
const result = Reflect.set(target, prop, value);
|
||||
if (!dataInstance.__destroyed__) {
|
||||
const path = `${dataInstance.constructor.name}:${propname}`;
|
||||
notifyChange(path, dataInstance, value, true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理方法拦截的逻辑(只拦截数据类中的方法,排除constructor)
|
||||
*/
|
||||
function handleMethodGet(dataInstance: any, target: any, prop: string | symbol, receiver: any): any {
|
||||
const value = Reflect.get(target, prop, receiver);
|
||||
const propname = prop.toString();
|
||||
|
||||
// 如果不是函数,直接返回
|
||||
if (typeof value !== 'function') {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 排除constructor方法
|
||||
if (propname === 'constructor') {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 排除以_和__开头的方法
|
||||
if (propname.startsWith('_')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 如果已经包装过,直接返回
|
||||
if (value.__kunpo_wrapped__) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const wrappedFunc = new Proxy(value, {
|
||||
apply: function (target: any, thisArg: any, args: any[]): any {
|
||||
// console.log('拦截到函数调用:', propname, args);
|
||||
let result = Reflect.apply(target, thisArg, args);
|
||||
const path = `${dataInstance.constructor.name}:${propname}`;
|
||||
notifyChange(path, dataInstance);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
// 标记已包装,避免重复包装
|
||||
Object.defineProperty(wrappedFunc, '__kunpo_wrapped__', {
|
||||
value: true,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
// 缓存包装后的函数
|
||||
Reflect.set(target, prop, wrappedFunc);
|
||||
return wrappedFunc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对象的内部属性
|
||||
*/
|
||||
function setupInternalProperties(dataInstance: any): void {
|
||||
// 使用构造函数名作为类名,与装饰器保持一致
|
||||
const className = dataInstance.constructor.name;
|
||||
dataInstance.__data_id__ = `${className}-${nextId++}`;
|
||||
dataInstance.__watchers__ = new Set();
|
||||
|
||||
// 定义不可枚举的内部属性,防止代码混淆问题
|
||||
Object.defineProperty(dataInstance, '__data_id__', {
|
||||
value: dataInstance.__data_id__,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
Object.defineProperty(dataInstance, '__watchers__', {
|
||||
value: dataInstance.__watchers__,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
Object.defineProperty(dataInstance, '__destroyed__', {
|
||||
value: false,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化已存在的直接子属性(不包含以_和__开头的属性,不进行深层递归)
|
||||
*/
|
||||
function initializeDirectProperties(dataInstance: any): void {
|
||||
for (const key in dataInstance) {
|
||||
// 跳过以_和__开头的属性和函数
|
||||
if (key.startsWith('_') || typeof dataInstance[key] === 'function') {
|
||||
continue;
|
||||
}
|
||||
const value = dataInstance[key];
|
||||
if (typeof value === 'object' && value !== null && !(value as any).__data_id__) {
|
||||
// 简单标记为已包装,但不创建深层代理
|
||||
Object.defineProperty(value, '__data_id__', {
|
||||
value: `${dataInstance.__data_id__}:${key}`,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function ProxyObject(dataInstance: any) {
|
||||
const handler = {
|
||||
set: (target: any, prop: string | symbol, value: any): boolean => {
|
||||
return handlePropertySet(dataInstance, target, prop, value)
|
||||
},
|
||||
get: (target: any, prop: string | symbol, receiver: any): any => {
|
||||
return handleMethodGet(dataInstance, target, prop, receiver)
|
||||
}
|
||||
};
|
||||
|
||||
setupInternalProperties(dataInstance);
|
||||
initializeDirectProperties(dataInstance);
|
||||
|
||||
return new Proxy(dataInstance, handler);
|
||||
}
|
||||
31
src/data/types.ts
Normal file
31
src/data/types.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 绑定器信息
|
||||
*/
|
||||
export interface BindInfo {
|
||||
/** 监听目标对象 */
|
||||
target: any;
|
||||
/** 属性或方法名 */
|
||||
prop: string | symbol;
|
||||
/** 监听的路径 */
|
||||
path: string;
|
||||
/** 回调函数 */
|
||||
callback: Function;
|
||||
/** 是否立即更新 */
|
||||
immediate: boolean;
|
||||
/** 是否为方法监听 */
|
||||
isMethod: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径变化事件
|
||||
*/
|
||||
export interface IDataEvent {
|
||||
/** 变化的属性路径 */
|
||||
path: string;
|
||||
/** 目标对象 */
|
||||
target: any;
|
||||
/** 是否是属性变化 */
|
||||
isProp?: boolean;
|
||||
/** 变化后的值 */
|
||||
value?: any;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { GComponent } from "fairygui-cc";
|
||||
import { data } from "../data/DataDecorator";
|
||||
import { Screen } from "../global/Screen";
|
||||
import { AdapterType, WindowType } from "../ui/header";
|
||||
import { IWindow } from "../ui/IWindow";
|
||||
@@ -49,6 +50,8 @@ export abstract class WindowBase extends GComponent implements IWindow {
|
||||
// 窗口自身也要设置是否吞噬触摸
|
||||
this.opaque = swallowTouch;
|
||||
this.bgAlpha = bgAlpha;
|
||||
// 初始化数据绑定(如果有 @dataclass 装饰器)
|
||||
data.initializeBindings(this);
|
||||
this.onInit();
|
||||
}
|
||||
|
||||
@@ -79,6 +82,8 @@ export abstract class WindowBase extends GComponent implements IWindow {
|
||||
* @internal
|
||||
*/
|
||||
public _close(): void {
|
||||
// 窗口关闭时 清理绑定信息
|
||||
data.cleanupBindings(this);
|
||||
this.onClose();
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
@@ -44,3 +44,7 @@ export { BytedanceCommon } from "./minigame/bytedance/BytedanceCommon";
|
||||
export { MiniHelper } from "./minigame/MiniHelper";
|
||||
export { WechatCommon } from "./minigame/wechat/WechatCommon";
|
||||
|
||||
/** 数据绑定相关 - 强类型数据绑定系统 */
|
||||
export { DataBase } from './data/DataBase';
|
||||
export { data } from './data/DataDecorator';
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @Description: 自定义组件扩展帮助类
|
||||
*/
|
||||
import { UIObjectFactory } from "fairygui-cc";
|
||||
import { data } from "../data/DataDecorator";
|
||||
import { debug } from "../tool/log";
|
||||
import { PropsHelper } from "./PropsHelper";
|
||||
import { _uidecorator } from "./UIDecorator";
|
||||
@@ -51,9 +52,19 @@ export class ComponentExtendHelper {
|
||||
// 自定义组件扩展
|
||||
const onConstruct = function (this: any): void {
|
||||
PropsHelper.serializeProps(this, pkg, name);
|
||||
// 初始化数据绑定(如果有 @dataclass 装饰器)
|
||||
data.initializeBindings(this);
|
||||
this.onInit && this.onInit();
|
||||
};
|
||||
ctor.prototype.onConstruct = onConstruct;
|
||||
|
||||
|
||||
const dispose = ctor.prototype.dispose
|
||||
const newDispose = function (this: any): void {
|
||||
data.cleanupBindings(this);
|
||||
dispose.call(this);
|
||||
};
|
||||
ctor.prototype.dispose = newDispose;
|
||||
// 自定义组件扩展
|
||||
UIObjectFactory.setExtension(`ui://${pkg}/${name}`, ctor);
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ export namespace _uidecorator {
|
||||
*/
|
||||
export function uiclass(groupName: string, pkgName: string, name: string, bundle?: string): Function {
|
||||
/** target 类的构造函数 */
|
||||
return function (ctor: any): void {
|
||||
// debug(`uiclass >${JSON.stringify(res)}<`);
|
||||
// debug(`uiclass prop >${JSON.stringify(ctor[UIPropMeta] || {})}<`);
|
||||
uiclassMap.set(ctor, {
|
||||
ctor: ctor,
|
||||
return function (ctor: any): any {
|
||||
// 检查是否有原始构造函数引用(由其他装饰器如 @dataclass 提供)
|
||||
const originalCtor = ctor;
|
||||
uiclassMap.set(originalCtor, {
|
||||
ctor: ctor, // 存储实际的构造函数(可能被包装过)
|
||||
props: ctor[UIPropMeta] || null,
|
||||
callbacks: ctor[UICBMeta] || null,
|
||||
controls: ctor[UIControlMeta] || null,
|
||||
@@ -78,8 +78,9 @@ export namespace _uidecorator {
|
||||
bundle: bundle || "",
|
||||
},
|
||||
});
|
||||
// 首次引擎注册完成后 动态注册窗口
|
||||
// 首次引擎注册完成后 动态注册窗口,使用实际的构造函数
|
||||
_registerFinish && WindowManager.dynamicRegisterWindow(ctor, groupName, pkgName, name, bundle || "");
|
||||
return ctor;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,10 +111,12 @@ export namespace _uidecorator {
|
||||
* @param {string} name 组件名
|
||||
*/
|
||||
export function uicom(pkg: string, name: string): Function {
|
||||
return function (ctor: any): void {
|
||||
return function (ctor: any): any {
|
||||
// 检查是否有原始构造函数引用(由其他装饰器如 @dataclass 提供)
|
||||
const originalCtor = ctor;
|
||||
// log(`pkg:【${pkg}】 uicom prop >${JSON.stringify(ctor[UIPropMeta] || {})}<`);
|
||||
uicomponentMap.set(ctor, {
|
||||
ctor: ctor,
|
||||
uicomponentMap.set(originalCtor, {
|
||||
ctor: ctor, // 存储实际的构造函数(可能被包装过)
|
||||
props: ctor[UIPropMeta] || null,
|
||||
callbacks: ctor[UICBMeta] || null,
|
||||
controls: ctor[UIControlMeta] || null,
|
||||
@@ -123,8 +126,9 @@ export namespace _uidecorator {
|
||||
name: name,
|
||||
}
|
||||
});
|
||||
// 首次引擎注册完成后 动态注册自定义组件
|
||||
// 首次引擎注册完成后 动态注册自定义组件,使用实际的构造函数
|
||||
_registerFinish && ComponentExtendHelper.dynamicRegister(ctor, pkg, name);
|
||||
return ctor;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,9 +159,11 @@ export namespace _uidecorator {
|
||||
*/
|
||||
export function uiheader(pkg: string, name: string, bundle?: string): Function {
|
||||
return function (ctor: any): void {
|
||||
// 检查是否有原始构造函数引用(由其他装饰器如 @dataclass 提供)
|
||||
const originalCtor = ctor;
|
||||
// log(`pkg:【${pkg}】 uiheader prop >${JSON.stringify(ctor[UIPropMeta] || {})}<`);
|
||||
uiheaderMap.set(ctor, {
|
||||
ctor: ctor,
|
||||
uiheaderMap.set(originalCtor, {
|
||||
ctor: ctor, // 存储实际的构造函数(可能被包装过)
|
||||
props: ctor[UIPropMeta] || null,
|
||||
callbacks: ctor[UICBMeta] || null,
|
||||
controls: ctor[UIControlMeta] || null,
|
||||
@@ -168,7 +174,7 @@ export namespace _uidecorator {
|
||||
bundle: bundle || "",
|
||||
}
|
||||
});
|
||||
// 首次引擎注册完成后 动态注册窗口header
|
||||
// 首次引擎注册完成后 动态注册窗口header,使用实际的构造函数
|
||||
_registerFinish && WindowManager.dynamicRegisterHeader(ctor, pkg, name, bundle || "");
|
||||
return ctor;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user