typescript - Hur man mappar typen på en egenskap till en konstruktörstyp


Jag försöker kartlägga typen av en egenskap till dess motsvarande konstruktörstyp, så string ska bli StringConstructor, number ska bli NumberConstructor...

Detta är vad jag har fått hittills, men intellisense visar unknown för både test.name?.type och test.age?.type

interface IPerson {
  name?: string
  age?: number
}

const test = {
  name: { type: String },
  age: { type: Number },
} as { [key in keyof IPerson]: {
  type: IPerson[key] extends string
    ? StringConstructor
    : IPerson[key] extends number
      ? NumberConstructor
      : unknown
}}

console.log(test.name?.type) console.log(test.age?.type)

console.log(test.name?.type)
console.log(test.age?.type)

vad behöver jag ändra för att kartlägga type korrekt?

URL till TypeScript REPL: https://www.typescriptlang.org/fr/play?strictNullChecks=false#code/JYOwLgpgTgZghgYwgAgJIEETALZwDbIDeAUMsiHNhAFzIDOYUoA5qfcAF43kCu2ARtGIBfYsVCRYiFKgAK0OgHsQRNhSq0GTEKzJxmEAPy0QfQVDYAHCGGNpMOfCLFgAnteQBZOJesATABV3CAAeAIA+ZABeVTIAbQBpZFBkAGsIV0UYZACAXVoSMjI3a1oAxNzkCAAPSBA-OnpGFmQAH2QeeogYUAg-NiLkQ2QAZWadAGFlLR4EMEULQbIyiqraiHrG0wFoNo6unpA+gaXhgDkzaCmQGbmFk8GVhMqauobkRX4AKwg5h9OiMgABQASielVESyWJggADchGRRKJiAhpmAqlAoAtorFyJRuIRkCVuGNtMxkMIADRsfQEonBWgXHZQCnUsjWMAFemlZAAeW+v3RVOQAHoReQ4dBKRisVAAIQiZBwRreXx9ILWEJyBTKcJiVE3RR4CAAOjwimYQOgspN6lNxJBovFpJY11u8wsBqUxrNFqtmIWJtphhNDqdyCZ5jdjFmHpR0yNpvNlutgY5IbDYol8Kg0tT8v1aPIigAogGWTFCniNIDibQXTpWTSDFy6xHLiyqVYbK2GXyBXMlY1QdFIhgsLgCF3hEOvD5-BrQtqoEoQHr44afcmgSBS+XbfjQ8FHVmG8xo1BY-cvYnfZbd2WbcGj9YT+LI1c0Ze7p6E1u-Q++7pi+EBvsCjpRGODiTsQQA

Svar #1

Om du antar att du har --strictNullChecks kompilatoralternativet aktiverat, så är typen för name och age egenskaperna i IPerson inte string och number; snarare är de string | undefined och number | undefined. Valfria egenskaper får automatiskt typen undefined tillagd till deras domän (om du aktiverar --exactOptionalPropertyTypes kompilatoralternativet är det lite mer komplicerat men det är fortfarande sant att om du läser en valfri egenskap kan den inkludera undefined). Och string | undefined är inte en utökning av string, så allt faller igenom till unknown och du blir ledsen.

Kanske det mest praktiska sättet att hantera detta är då att bara testa extends string | undefined istället för extends string, eftersom string extends string | undefined är sant:

const test: { [K in keyof IPerson]: {
  type: IPerson[K] extends string | undefined ? StringConstructor
  : IPerson[K] extends number | undefined ? NumberConstructor
  : unknown
} }
  = {
  name: { type: String },
  age: { type: Number },
};

/* const test: {
    name?: {
        type: StringConstructor;
    } | undefined;
    age?: {
        type: NumberConstructor;
    } | undefined;
} */

Observera att det finns mer generella lösningar som tar en egenskap av typen string | number och gör om den till ett värde av typen StringConstructor | NumberConstructor; det vill säga, de kommer att distribuera din typoperation över unionstyper; eller lösningar som tar en avbildningstyp som [string, StringConstructor] | [number, NumberConstructor] | ... och använder den för att programmatisk översätta dina typer så att du bara kan lägga till nya element som [boolean, BooleanConstructor] och det skulle fungera. Men alla dessa ligger utanför räckvidden för frågan som ställdes, så jag kommer inte gå in på dem för denna fråga.

Lekplatslänk till kod

Svar #2

Jag är ganska säker på att problemet är att typerna för IPerson.name och IPerson.age båda är valfria. Så du kan inte testa att egenskapen utvidgar string eller number eftersom den faktiskt utvidgar (string | undefined) och (number | undefined). Därför kommer din nästlade ternära alltid att utvärderas till unknown.

För att åtgärda detta kan du ändra gränssnittet för IPerson så att båda egenskaperna är obligatoriska

Relaterade artiklar