装饰器字段不能在 Super class 中使用 TypeScript 工作?

decorator field not working in supper class with TypeScript?

在文件中 base.ts

export const INCLUDED_FIELDS = new Map<string, string[]>();
export function JsonInclude(cls: any, name: string){
   let clsName = cls.constructor.name;
    let list: string[];

    if (INCLUDED_FIELDS.has(clsName)) {
        list = INCLUDED_FIELDS.get(clsName);
    } else {
        list = [];
        INCLUDED_FIELDS.set(clsName, list);
    }

    list.push(name);
}

export default class Base{
    @JsonInclude
    public  name: string;

    toJSON(): { [name: string]: any } {
        let json = {};
        let include = INCLUDED_FIELDS.get(this.constructor.name);

        cc.log(include);
        Object.getOwnPropertyNames(this).filter(name => include.indexOf(name) > 0).forEach(name => {
            cc.log("adding " + name);
            json[name] = this[name];
        });

        return json;
    }

}

并且在staff.ts

export default class Staff extends Base {
   
    @JsonInclude
    public salary: number = 1000;

    @JsonInclude
    public level: number = 1;

    @JsonInclude
    public comment:string = "";
}

似乎 Base class 中的 name 字段不会添加到 INCLUDED_FIELDS 列表中?

那是因为装饰器不被继承。当 class 被定义而不是被实例化时,装饰器被调用。 Class 继承比复制粘贴更复杂,如果你仔细研究它,你就会知道你还有 super 调用,它允许你访问这样的继承 class (证明base class 真的不仅仅是在编译过程中硬粘贴)

也就是说,当继承 class 时,这样的 class 已经声明了 。因此,装饰器不会触发 - 它根本无法触发。

你可以在这个例子中很好地看到它:https://stackblitz.com/edit/ts-decorator-inheritance

Base class 中的 name 字段似乎不会添加到 INCLUDED_FIELDS 列表中”。实际上,name 将被添加到映射中,但它存储在其他键名中。你的地图看起来像

Map {
  'Base' => [ 'name' ],
  'Staff' => [ 'salary', 'level', 'comment' ],
}

对于 Base class,您为 name 字段应用 JsonInclude 装饰器。现在 JsonInclude - cls 的第一个参数将是 Base,这意味着 cls.constructor.name 将 return "Base" 这是第一个键名地图。

同样的情况Staffclass,cls.constructor.name会return"Staff"

最后,您在 Staff class 的实例上调用 toJSON:

const staff = new Staff();
console.log(staff.toJSON());

this context in line let include = INCLUDED_FIELDS.get(this.constructor.name); is Staff instance (this.constructor.name return "Staff"), 那么你就得到 'Staff' => [ 'salary', 'level', 'comment' ] 地图的价值。

解决方法,一个“class类型”只使用一个键名。这是一个函数什么 return class class 的“根”名称 class.

function getRootClassName(cls: any): string {
  let clsName = cls.constructor.name;

  let parentCls = Object.getPrototypeOf(cls);
  while (parentCls.constructor.name !== "Object") {
    clsName = parentCls.constructor.name;
    parentCls = Object.getPrototypeOf(parentCls);
  } 
  return clsName;
}

示例:Object->Base->Staff ===> 该函数将为“Base”实例或“Staff”实例return“Base”字符串。

现在,您的装饰器将像:

export default function JsonInclude(cls: any, name: string) {
  let clsName = getRootClassName(cls);
  let list: string[];

  if (INCLUDED_FIELDS.has(clsName)) {
    list = INCLUDED_FIELDS.get(clsName);
  } else {
    list = [];
    INCLUDED_FIELDS.set(clsName, list);
  }

  list.push(name);
}

toJSON 函数将类似于:

toJSON(): { [name: string]: any } {
    let json: any = {};
    let include = INCLUDED_FIELDS.get(getRootClassName(this)) || []; // get key name

    for (const name in this) {
      if (include.indexOf(name) >= 0) { // === 0 for the first item of include
        console.log("adding " + name);
        json[name] = (this as any)[name];
      }
    }

    return json;
  }

我使用 for...in 而不是 getOwnPropertyNames,因为 getOwnPropertyNames 只是 return 直接在给定对象(而不是来自父对象)中找到的所有属性

最终代码:

base.ts

export const INCLUDED_FIELDS = new Map<string, string[]>();

function getRootClassName(cls: any): string {
  let clsName = cls.constructor.name;

  let parentCls = Object.getPrototypeOf(cls);
  while (parentCls.constructor.name !== "Object") {
    clsName = parentCls.constructor.name;
    parentCls = Object.getPrototypeOf(parentCls);
  } 
  return clsName;
}

export default function JsonInclude(cls: any, name: string) {
  let clsName = getRootClassName(cls);
  let list: string[];

  if (INCLUDED_FIELDS.has(clsName)) {
    list = INCLUDED_FIELDS.get(clsName);
  } else {
    list = [];
    INCLUDED_FIELDS.set(clsName, list);
  }

  list.push(name);
}

export default class Base {
  @JsonInclude
  public name: string = '';

  toJSON(): { [name: string]: any } {
    let json: any = {};
    let include = INCLUDED_FIELDS.get(getRootClassName(this)) || [];

    for (const name in this) {
      if (include.indexOf(name) >= 0) {
        console.log("adding " + name);
        json[name] = (this as any)[name];
      }
    }

    return json;
  }
}

staff.ts

export default class Staff extends Base {

  @JsonInclude
  public salary: number = 1000;

  @JsonInclude
  public level: number = 1;

  @JsonInclude
  public comment: string = "";
}