typescript - Varför går typen `keyof T` förlorad när man skapar ett nytt objekt?


Jag arbetar med mongoose-frågor som jag paketerar i mer abstrakta funktioner. Jag får problem med typisering, vilket kan brytas ner till följande problem:

function x<T extends object, K extends keyof T> (
  obj: T,
  key: K
) {
  // Type '{ [x: string]: string; }' is not assignable to
  // type '{ [Q in K]: string; }'.(2322)
  const y: {[P in K]: {[Q in P]: string}}[K] = { [key]: "ho" };
}

Detta fel är meningslöst eftersom den begränsade typen mycket tydligt och explicit uppfylls. Varför förloras denna typinformation? Hur kan TS ens anta att typen på nyckeln är string? Den skulle ju också kunna vara number i allmänhet, eller hur?!

Lekplats

Svar #1

Det finns en långvarig bugg eller begränsning i TypeScript, rapporterad på microsoft/TypeScript#13948, där en beräknad egenskapsnyckel vars typ inte är en enda strängliteraltyp vidgas hela vägen till string, vilket resulterar i en utgångstyp med en strängindexsignatur.

Så om du har {[k]: ⋯} där k är av en generisk typ,

function foo<K extends string>(k: K) {
  const o = { [k]: 1 };
  /* const o: { [x: string]: number;} */
}

eller där k är av en unionstyp,

{
  const k = Math.random() < 0.5 ? "a" : "b"
  const o = { [k]: 1 };
  /* const o: { [x: string]: number;} */
}

eller där k är av en mönster-mall-litteraltyp som implementerat i microsoft/TypeScript#40598:

{
  const k: `xyz${string}` = "xyz123";
  const o = { [k]: 1 };
  /* const o: { [x: string]: number;} */
}

ditt objekt kommer att ha en strängindexsignatur trots att det nästan säkert finns en mer specifik typ som skulle fungera.

Problemet är att ett indexsignatur är så enkelt jämfört med den "riktiga" typen. Antagligen behöver du något som en distributiv objekttyp av formen {[P in K]: {[Q in P]: V}}[K] där K är typen av k och V är typen av v i {[k]: v}. (Den "uppenbara" Record<K, V> fungerar inte i fall där K kan vara en unionstyp.) Detta är typen som du kommer fram till i din fråga. Men en sådan typ är ganska komplicerad och svår för kompilatorn att resonera om i abstrakt form. Detta är en av anledningarna till att problemet inte har lösts än: de vill inte att varje beräknad nyckel ska ta mer kompileringstid och resultera i överdrivet komplexa typer. Se GitHub-ärendet för mer detaljerad information.

Så tills dess att detta problem löses, om du vill se en distributiv objekttyp måste du skriva den själv och försäkra dig om att värdet är av den typen... som du har gjort i frågekoden.

Lekplatslänk till kod