typescript - hur man undviker kompileringsfel för properties med enumerable: false


Jag försöker lära mig TS och jag stötte på ett konstigt fall.

Jag har ett objekt med en egenskap inställd som icke-uppräkningsbar:

let person = {
  name: 'Harry'
}

Object.defineProperty(person,'salary',{enumerable: false, value : 15});

console.log(person.salary); // 15

Detta fungerar vid körning, men kompilatorn kommer att ge ett fel:

Egenskapen 'salary' finns inte på typen '{ name: string; }'

vilket är väntat.

Jag skulle kunna kringgå denna inställning genom att sätta personens typ som vilken som helst, men det känns inte som en ren lösning.

Finns det ett annat sätt att göra det på? Operatorn för icke-null-säkerhet fungerar inte för detta.

Jag skulle uppskatta om någon kan dela med sig av lite kunskap om detta.

Svar #1

Även om du skulle kunna ändra typannoteringen för person, kanske så här:

let person: { name: string; salary?: number } = {
  name: 'Harry'
};

eller till och med:

let person = {
  name: 'Harry'
} as { name: string; salary: number };

Den säkraste metoden är förmodligen att (miss)bruka assertionsfunktioner i version 3.7:

function defineProperty<
    O,
    Property extends PropertyKey,
    Value
>(o: O, key: Property, attributes: PropertyDescriptor & ThisType<any> & { value: Value }): asserts o is O & Record<Property, Value> {
    Object.defineProperty(o, key, attributes);
}

Det är i princip en omslagsfunktion för Object.defineProperty som berättar för TypeScript att om den här funktionen inte kastar ett fel, då är typen av det givna värdet o O & Record<Property, Value>.

Så när du använder det får du en typ som denna:

defineProperty(person,'salary',{enumerable: false, value : 15});

person
// ^? { name: string; } & Record<"salary", number>

Lekplats


Och om du verkligen vill, kan du också göra den resulterande typen renare genom att ändra på påståendet till detta:

asserts o is (O & Record<Property, Value> extends infer T extends O ? { [K in keyof T]: T[K] } : O)

Som du kan se här när du hovrar över person, är typen nu { name: string; salary: number; } istället för en ful snittning.