Я не могу заставить этот код работать.
import "reflect-metadata";
export class Castable {
[key: string]: any;
constructor(source: any) {
console.info("source: ");
console.info(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.info("after constructor this: ");
console.info(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.info("after new: ");
console.info(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 }
Назначение в конструкторе кажется успешным, но на самом деле оказывается пустым. На самом деле это глубже, но это минимальная структура для изоляции проблемы. Почему он будет пустым?
Это вызвано параметрами компилятора useDefineForClassFields
или их отсутствием. Потому что, если он не установлен вручную, его значение по умолчанию соотносится с опцией target
. Из документов:
Этот флаг используется при переходе на следующую стандартную версию полей класса. TypeScript представил поля классов за много лет до того, как он был ратифицирован в TC39. Последняя версия будущей спецификации отличается от реализации TypeScript поведением во время выполнения, но с тем же синтаксисом.
Этот флаг переключает на предстоящее поведение среды выполнения ECMA.
По умолчанию:
true
если цель ES2022 или выше, включая ESNext,false
в противном случае.
Не стесняйтесь читать подробную предысторию. Краткое пояснение к вашему делу выглядит так:
/** --- TreeRoot --- */
class MyConfigRoot extends Castable {
result: boolean; // 👈
count: number;
}
Эти аннотации только для типов в TS раньше были безвредными и не имели никакого эффекта во время выполнения JS. Однако при введении 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, вы также можете использовать ключевое слово declare
в коде TS v3.7+ для точного управления компиляциями JS.
/** --- 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
});
}
}
Теперь я понимаю. Спасибо за подробный ответ с рабочим примером!
Смотрите также: stackoverflow.com/a/70250602/3617380