Files
oops-plugin-framework/assets/module/common/CCView.ts
dgflash 2efe3a1982 优化 ECS 和 MVVM 相关代码的性能与可读性
- ECS.ts: 简化实体创建的类型转换,修复实体复用时的 isValid 标记逻辑
- ECSEntity.ts: 添加辅助方法注释,优化组件属性访问
- VMBase.ts: 优化路径解析逻辑
- CCView.ts: 重构 MVVM 路径替换和 VM 组件获取逻辑,提升性能
- GamePrefabDecorator.ts: 优化装饰器中的类型转换

所有变更均为内部实现优化,不影响对外 API
2026-02-26 22:52:49 +08:00

239 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* @Author: dgflash
* @Date: 2021-11-11 19:05:32
* @LastEditors: dgflash
* @LastEditTime: 2022-09-06 17:20:51
*/
import type { Component } from 'cc';
import type { ecs } from '../../libs/ecs/ECS';
import { ECSModel } from '../../libs/ecs/ECSModel';
import { VM } from '../../libs/model-view/ViewModel';
import { VMBase } from '../../libs/model-view/VMBase';
import type { CCEntity } from './CCEntity';
import type { UICtor } from '../../types/Types';
import { GameComponent } from './GameComponent';
/**
* ECS 游戏显示对象组件
*
* 功能介绍:
* 1. 对象拥有 cc.Component 组件功能与 ecs.Comp 组件功能
* 2. 对象自带全局事件监听、释放、发送全局消息功能
* 3. 对象管理的所有节点摊平直接通过节点名获取cc.Node对象
* 4. 支持可选的 MVVM 功能(通过 mvvm 属性启用)
*
* 应用场景
* 1. 网络游戏,优先有数据对象,然后创建视图对象,当释放视图组件时,部分场景不希望释放数据对象
*
* @example
// 不使用 MVVM 的组件
@ccclass('RoleViewComp')
@ecs.register('RoleView', false)
export class RoleViewComp extends CCView<Role> {
@property({ type: sp.Skeleton, tooltip: '角色动画' })
spine: sp.Skeleton = null!;
onLoad(){
super.onLoad();
}
}
// 使用 MVVM 的组件
@ccclass('LoadingViewComp')
@ecs.register('LoadingView', false)
export class LoadingViewComp extends CCView<Initialize> {
protected mvvm = true; // 启用 MVVM 功能
data: LoadingData = {
finished: 0,
total: 0,
progress: "0",
prompt: ""
};
onLoad(){
super.onLoad();
}
reset(): void {
// 重置逻辑
}
}
*/
export abstract class CCView<T extends CCEntity> extends GameComponent implements ecs.IComp {
static tid = -1;
static compName: string;
canRecycle!: boolean;
ent!: T;
tid = -1;
//#region MVVM 功能相关(仅在 mvvm = true 时使用)
/** 是否启用 MVVM 功能(子类可覆盖为 true */
protected mvvm = false;
/**
* MVVM 绑定的标签,延迟初始化以节省内存
* 仅在启用 MVVM 时创建
*/
protected tag?: string;
/**
* 需要绑定的私有数据
* 注意:子类应该显式初始化此属性
*/
protected data?: object;
/**
* 组件加载时调用
* 注意:如果子类需要覆盖此方法,必须调用 super.onLoad()
*/
onLoad() {
if (!this.mvvm) return;
// onBind 语义为"绑定初始化",与 data 是否存在解耦,始终调用
this.onBind();
if (this.data === undefined || this.data === null) {
console.warn(`[CCView] ${this.constructor.name}: mvvm=true 但 data 未定义VM 绑定已跳过`);
return;
}
this.initializeVM();
}
/**
* 初始化 MVVM 功能
* @private
*/
private initializeVM() {
// 使用 split/join 替换 uuid 中所有的点,避免 VM.add 内部校验失败
const uuid = this.node.uuid;
this.tag = `_temp<${uuid.split('.').join('')}>`;
VM.add(this.data!, this.tag);
const comps = this.getVMComponents();
const len = comps.length;
const tag = this.tag;
for (let i = 0; i < len; i++) {
this.replaceVMPath(comps[i], tag);
}
}
/**
* 在 onLoad 完成和 start() 之前调用,你可以在这里进行初始化数据等操作
* 注意:仅在 mvvm = true 时调用
* @protected
*/
protected onBind() { }
/**
* 在 onDestroy() 后调用,此时仍然可以获取绑定的 data 数据
* 注意:仅在 mvvm = true 时调用
* @protected
*/
protected onUnBind() { }
/**
* 替换 VM 组件的路径
* @private
*/
private replaceVMPath(comp: VMBase, tag: string) {
if (comp.templateMode) {
const pathArr = comp.watchPathArr;
const len = pathArr.length;
for (let i = 0; i < len; i++) {
pathArr[i] = pathArr[i].replace('*', tag);
}
} else if (comp.watchPath.charCodeAt(0) === 42) { // 42 是 '*' 的字符码
comp.watchPath = comp.watchPath.replace('*', tag);
}
}
/**
* 获取当前节点下属于本 CCView 管辖的 VMBase 组件(排除嵌套启用 MVVM 的子 CCView 管辖范围)
* @private
*/
private getVMComponents(): VMBase[] {
const comps = this.node.getComponentsInChildren(VMBase);
if (comps.length === 0) return comps;
const parents = this.node.getComponentsInChildren(CCView);
const myUuid = this.uuid;
// 单次遍历:同时判断是否有嵌套 MVVM CCView并构建过滤集合
const filterSet = new Set<VMBase>();
const len = parents.length;
let hasNested = false;
for (let i = 0; i < len; i++) {
const p = parents[i];
if (p.uuid !== myUuid && p.mvvm) {
hasNested = true;
const childComps = p.node.getComponentsInChildren(VMBase);
const childLen = childComps.length;
for (let j = 0; j < childLen; j++) {
filterSet.add(childComps[j]);
}
}
}
if (!hasNested) return comps;
const result: VMBase[] = [];
const compsLen = comps.length;
for (let i = 0; i < compsLen; i++) {
if (!filterSet.has(comps[i])) {
result.push(comps[i]);
}
}
return result;
}
//#endregion
/** 从父节点移除自己 */
remove() {
if (!this.ent) {
console.error(`组件 ${this.name} 移除失败,实体不存在`);
return;
}
if (this.tid < 0) {
console.error(`组件 ${this.name} 移除失败,组件未注册 (tid=${this.tid})`);
return;
}
const cct = ECSModel.compCtors[this.tid];
if (!cct) {
console.error(`组件 ${this.name} 移除失败,组件构造函数不存在 (tid=${this.tid})`);
return;
}
this.ent.removeUi(cct as unknown as UICtor);
this.ent = null!; // 清空引用,避免内存泄漏
}
/**
* 组件销毁时调用
* 注意:如果子类需要覆盖此方法,必须调用 super.onDestroy()
*/
protected onDestroy() {
// 只有启用了 MVVM 时才执行清理
if (this.mvvm) {
this.onUnBind();
// 解除全部引用
if (this.tag) {
VM.remove(this.tag);
this.tag = undefined;
}
this.data = undefined;
}
super.onDestroy();
}
abstract reset(): void;
}