英文:
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
});
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论