新对象将是空的。

huangapple go评论71阅读模式
英文:

new object will be empty

问题

无法使此代码工作。

import "reflect-metadata";

export class Castable {
  [key: string]: any;
  constructor(source: any) {
    console.log("source:");
    console.log(source);
    Object.getOwnPropertyNames(source).forEach((propertyKey) => {
      const designType = Reflect.getMetadata("design:type", this, propertyKey);
      const customType = Reflect.getMetadata("custom:type", this, propertyKey);
      const type = customType !== undefined ? customType : designType;
      this[propertyKey] = this.convert(
        source[propertyKey],
        propertyKey,
        type,
        0
      );
    });
    console.log("after constructor this:");
    console.log(this);
  }

  private convert(
    source: any,
    propertyKey: string,
    type: any,
    depth: number
  ): any {
    if (type === undefined) {
      return source;
    }
    switch (type.name) {
      case "Number":
        return Number(source);
      case "String":
        return String(source);
      case "Boolean":
        return source.toString() === "true";
      default:
        return new type(source);
    }
  }
}

/** --- TreeRoot --- */
export class MyConfigRoot extends Castable {
  result: boolean;
  count: number;
}

function init() {
  const json = '{"result":true, "count":32}';
  let input = JSON.parse(json);
  let newR = new MyConfigRoot(input);
  console.log("after new:");
  console.log(newR);
}

init();

在使用 Reflect.getMetadata 获取类型后,执行类型检查。这段代码将导致一个空的新对象。

> node ./dist/test.js

source:
{ result: true, count: 32 }
after constructor this:
MyConfigRoot { result: true, count: 32 }
after new:
MyConfigRoot { result: undefined, count: undefined }

构造函数中的赋值似乎成功了,但实际上为空。问题实际上比这个更深,但这是一个用来隔离问题的最小结构。为什么会为空呢?

英文:

I can't get this code to work.

import "reflect-metadata";

export class Castable {
  [key: string]: any;
  constructor(source: any) {
    console.log("source: ");
    console.log(source);
    Object.getOwnPropertyNames(source).forEach((propertyKey) => {
      const designType = Reflect.getMetadata("design:type", this, propertyKey);
      const customType = Reflect.getMetadata("custom:type", this, propertyKey);
      const type = customType !== undefined ? customType : designType;
      this[propertyKey] = this.convert(
        source[propertyKey],
        propertyKey,
        type,
        0
      );
    });
    console.log("after constructor this: ");
    console.log(this);
  }

  private convert(
    source: any,
    propertyKey: string,
    type: any,
    depth: number
  ): any {
    if (type === undefined) {
      return source;
    }
    switch (type.name) {
      case "Number":
        return Number(source);
      case "String":
        return String(source);
      case "Boolean":
        return source.toString() === "true";
      default:
        return new type(source);
    }
  }
}

/**  --- TreeRoot ---  */
export class MyConfigRoot extends Castable {
  result: boolean;
  count: number;
}

function init() {
  const json = '{"result":true, "count":32}';
  let input = JSON.parse(json);
  let newR = new MyConfigRoot(input);
  console.log("after new: ");
  console.log(newR);
}

init();

After getting the type with Reflect.getMetadata, type checking is performed.
This code would result in an empty new object.

> node ./dist/test.js

source:
{ result: true, count: 32 }
after constructor this:
MyConfigRoot { result: true, count: 32 }
after new:
MyConfigRoot { result: undefined, count: undefined }

The assignment in constractor seems to succeed, but actually comes up empty.
It is actually deeper than that, but it is a minimal structure to isolate the problem. Why would it be empty?

答案1

得分: 1

这是由于 useDefineForClassFields 编译选项引起的,或者是缺少它。因为如果没有手动设置,它的默认值与 target 选项相关。从文档中可以看到:

> 此标志用于迁移到即将发布的类字段的标准版本。 TypeScript 在 TC39 正式批准之前多年前就引入了类字段。即将发布的规范的最新版本具有与 TypeScript 实现不同的 不同的运行时行为,但具有相同的语法。
>
> 此标志切换到即将发布的 ECMA 运行时行为。
>
> 默认值:
如果目标是 ES2022 或更高版本,包括 ESNext,则为 true,否则为 false

请随意阅读详细的背景说明。简要解释如下:

/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
  result: boolean; // 👈
  count: number;
}

在 TypeScript 中的这些仅类型注释曾经是无害的,没有 JavaScript 运行时效果。但是当引入 useDefineForClassFields 时,编译结果会发生变化。

  • useDefineForClassFields: true
/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
    constructor() {
        super(...arguments);
        Object.defineProperty(this, "result", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "count", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
}
  • useDefineForClassFields: false
/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
}

这就是您观察到的行为。

除了调整 TS 编译器选项外,您还可以在 TS v3.7+ 代码中使用 declare 关键字来精细控制 JS 编译。

/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
  declare result: boolean; // 👈 使用 declare 关键字
  count: number;
}

// 现在 `useDefineForClassFields: true` 编译为:

/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
    constructor() {
        super(...arguments);
        Object.defineProperty(this, "count", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
}
英文:

This is caused by the useDefineForClassFields compilerOptions, or the lack of it. Because if not manually set, its default value is correlated to target option. From docs:

> This flag is used as part of migrating to the upcoming standard version of class fields. TypeScript introduced class fields many years before it was ratified in TC39. The latest version of the upcoming specification has a different runtime behavior to TypeScript’s implementation but the same syntax.
>
>This flag switches to the upcoming ECMA runtime behavior.
>
> Default:
true if target is ES2022 or higher, including ESNext, false otherwise.

Feel free to read the detailed backstory. Short explainer to your case goes like this:

/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
  result: boolean; // 👈
  count: number;
}

These type-only annotations in TS used to be harmless and have no JS runtime effect. However when useDefineForClassFields is introduced, the compilation result changes.

  • useDefineForClassFields: true
/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
    constructor() {
        super(...arguments);
        Object.defineProperty(this, "result", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "count", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
}
  • useDefineForClassFields: false
/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
}

Thus the behavior you observed.


Besides tuning TS compiler option, you can also use the declare keyword in your TS v3.7+ code to fine control the JS compilations.

/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
  declare result: boolean; // 👈 declare keyword
  count: number;
}

// Now `useDefineForClassFields: true` COMPILES TO:

/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
    constructor() {
        super(...arguments);
        Object.defineProperty(this, "count", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
}

huangapple
  • 本文由 发表于 2023年1月10日 10:51:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75064978.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定