typescript - Avancerade villkorstyper - Infer-syntax
Introduktion
Jag har lekt med några avancerade funktioner i TS och hittat några häftiga saker man kan göra. En häftig sak som fångade mitt öga var att extrahera alla typer av varje element i en array. Dock tycker jag att syntaxen i detta särskilda exempel är förvirrande.
Den mest förvirrande delen av de två exemplen jag har markerat direkt under denna kommentar finns i båda exemplen med en liten variation.
Problematiskt rad
T extends (infer U)[]
Exempel 1
type ArrayTypes<T> =
T extends (infer U)[]
? U
: never
let arr = [1, "2", []];
type test = ArrayTypes<typeof arr> // type test = string | number | any[]
Exempel 2
type ArrayTypes<T> =
T extends (infer U)
? U
: never
let arr = [1, "2", []];
type test = ArrayTypes<typeof arr> // type test = (string | number | any[])[]
Fråga
Nu förstår jag generics (för det mesta) och så jag förstår vad T representerar och jag förstår också vad extends gör (för det mesta). Jag förstår också att U kan användas för att sluta sig till en typ när som helst en generisk typ kunde användas.
Det jag inte riktigt förstår specifikt är vad och hur parenteserna och hakparenteserna fungerar i båda exemplen. Jag skulle uppskatta en fullständig genomgång väldigt mycket. Om du behöver förklara mer om hur infer eller extends fungerar, varsågod, men som jag sa, jag tror att jag förstår de begreppen... kanske inte. En sak som verkligen är förvirrande är att om jag tar bort ()/parenteserna så blir allt helt fel. Varför är det så?
Har jag rätt i att tänka följande antaganden?
- bakom kulisserna, i typrummet, är parenteser ett striktare sätt att definiera en unionstyp? och därför kommer TypeScript att omvandla följande
type test2 = string | number
in i
type test2 = ( string | number )
Lägg märke till parenteserna.
- (infer U) säger - definiera en ny unionstyp, men infer den och tilldela den till U. Det skapar också typerna för en array?
T extends (infer U)[]
Tack :)
Svar #1
Parenteser i typer används bara för gruppering och för att lösa tvetydigheter; de ändrar inte typen de omger. Till exempel, med typerna
type A = { a: 1, c: 3 };
type B = { b: 2, c: 3 };
Vad blir detta typ då?
type Z = keyof A | B; // ??
Med parenteser kan du vara tydlig med hur du menar att det ska tolkas:
type X = (keyof A) | B; // "a" | "c" | B
type Y = keyof (A | B); // "c"
Det visar sig att keyof binder mer strikt till A än unionoperatorn | gör, så Z är ekvivalent med X... så om du menar Y behöver du de där parenteserna.
Den (som alltmer blir föråldrade) TypeScript-specifikationsdokumentet säger:
Parenteser krävs kring union, snitt, funktion eller konstruktörstyper när de används som arrayelementtyper; kring union, funktion eller konstruktörstyper i snittyper; och kring funktion eller konstruktörstyper i unionstyper.
När du är osäker, lägg till parenteser.
Fyrkantiga parenteser i typer har faktiskt en mängd olika betydelser:
När en typ
Tomedelbart följs av ett par tomma hakparenteser, betyder det en array vars element är av typenT, och är ekvivalent medArray<T>. Så{a: string}[]är en array vars element är av typen{a: string}, och är ekvivalent medArray<{a: string}>. (På samma sätt, om en sådan typ omedelbart föregås av modifierarenreadonly, som ireadonly T[], är den ekvivalent medReadonlyArray<T>.)När en typ
Tomedelbart följs av en annan typUinnesluten i hakparenteser, (d.v.s.T[U])... är hakparenteserna inte tomma, betyder det att du gör en uppslagning eller indexering av typen på egenskapen hosTvars nyckel är av typenU. Så{a: string}["a"]är typen påa-egenskapen hos{a: string}... nämligenstring. OchArray<boolean>[number]är typen du får när du indexerar in i enArray<boolean>med ennumber-nyckel... nämligenboolean.När hakparenteser innehåller en kommaavgränsad lista med noll eller flera typer som
[]eller[T]eller[T,U]eller[T,U,V], etc., (och om det är noll eller en måste du vara försiktig så att det inte förväxlas med array- eller uppslagsnotationen ovan), då anger du en tuple-typ, en array med ett fast antal element som kan ha olika typer i en fast ordning. Tuple-typer kan också föregås avreadonlyför att göra read-only tuples, och det finns andra roliga saker som händer med tuples, som rest- och valfria element i tuple-typer och variadiska tuple-typer, såreadonly [0, ...[1, 2, 3], 4?, ...[5?, 6?, 7?], ...8[]]är en giltig TS-typ (i TS4.0+).
Beväpnade med det, låt oss titta på dessa exempel:
type ArrayTypes<T> =
T extends (infer U)[]
? U
: never
Detta säger: givet en typ T, om den utvidgar någon arraytyp U[] där U ska härledas, returnera då U, annars returnera never. infer behöver fästas vid U. Av någon anledning binder [] tätare än infer, och därför skulle T extends infer U[] tolkas som T extends infer (U[])... vilket är ogiltigt eftersom du endast kan infer en ny typvariabel, inte en funktion av en sådan variabel. Därför behöver du parenteserna.
let arr = [1, "2", []];
type test = ArrayTypes<typeof arr> // type test = string | number | never[]
Det är vettigt, eller hur? arr antas vara av typen Array<string | number | Array<never>> eller motsvarande (string | number | never[])[], och därför antas ArrayTypes<typeof arr> ge U som string | number | never[] och det är vad du får. (Observera att värdet [] antas vara av typen never[] i den TS-version jag använder och inte any[].)
Observera också, som en parentes, att uppslagstypen ovan med en number-nyckel gör samma sak utan att behöva infer:
type ArrayTypes<T> = T extends any[] ? T[number]: never;
Nästa exempel:
type ArrayTypes<T> =
T extends (infer U)
? U
: never
Här är parenteserna inte nödvändiga eftersom det inte finns någon tvetydighet. Du kan skriva T extends infer U ? U : never. Detta betyder: givet en typ T som ärver någon infererad typ U, returnera U; annars returnera never. Men detta verkar lite fånigt, eftersom U alltid kommer att infereras som T, och därför kommer ArrayTypes<T> alltid att vara T, oavsett vad:
let arr = [1, "2", []];
type test = ArrayTypes<typeof arr> // type test = (string | number | never[])[]
Typen test kommer att vara precis samma som typeof arr, vilket är Array<string | number | Array<never>>, eller ekvivalent, (string | number | never[])[].
Jag hoppas att det klargör saker för dig. Lycka till!
Lekplatslänk till kod
