typescript - Finns det ett sätt att extrahera det sista elementet/de sista elementen, med typ, från en union av tupler med olika längd?
Jag har lagt ett exempel här.
Jag har ett antal tuppler av olika längder som alla slutar med samma typrsekvens.
type T1 = [number, string, number[]];
type T2 = [number, number, string, number[]];
type T3 = [number, number, number, string, number[]];
Givet ett objekt som är en union av dessa typer, känner jag till typen av det sista elementet/dessa element, och jag kan till och med få TypeScript att härleda dessa typer åt mig, men jag kan inte hitta ett sätt för TypeScript att lista ut det när jag extraherar ett element från tuplen:
function doesnt_work(arg: T1|T2|T3) : [string, number[]] {
return [arg[arg.length-2], arg[arg.length-1]];
}
Observera att ts vet att arg[0] har typen number, och arg[1] har typen number|string. Jag hoppas kunna hitta ett sätt att få det att ta reda på typen av arg[arg.length-1] (eller arg.pop(), eller arg.slice(-1) osv).
Jag försökte arg[arg.length-n], arg.slice(-n), arg.pop() osv.
Jag hoppades att TypeScript skulle härleda typen, eftersom det vet vilken typ det sista elementet i tuplen har. Men det härleder bara typen som om jag hade indexerat tuplen med ett okänt nummer.
Svar #1
TypeScript ger för närvarande inte starka typer för relativ indexering i tuple-typer.
Det finns sådana saker som ledande restelement i tuple-typer som [...unknown[], string, number[]], men detta gör bara att man lätt kan representera en supertyp av T1 | T2 | T3; det ger dig inte starka typangivelser för arg[arg.length - 2]. Det är mer användbart för anropare av din funktion än för implementeringen.
Du får inte en stark typ från arg[arg.length - 2] eftersom typen av arg.length - 2 bara är number; det finns inget inbyggt stöd för typnivåmanipulation av numeriska literaltyper, som begärts i det långvariga öppna ärendet microsoft/TypeScript#26382. Även om det skulle ändras någon dag, skulle det att ha T1 | T2 | T3 som en union fortfarande sannolikt orsaka problem, eftersom arg.length - 2 skulle utvärderas som 1 | 2 | 3 vilket skulle vara okorrelerat med typen av arg (se ms/TS#30581). Så det är troligen en återvändsgränd.
Det fanns en funktionförfrågan på microsoft/TypeScript#47660 att ge starka typer när man anropar at()-arraymetoden med negativa index, men den avslogs som "för komplicerad".
Och även om du skulle kunna försöka göra det själv, innebär bristen på stöd för aritmetik med numeriska literaler att du måste hoppa igenom några fula hinder, som visas i den här kommentaren:
type Reverse<T extends readonly any[], U extends any[] = []> =
T extends readonly [infer F, ...infer R] ? Reverse<R, [F, ...U]> : U
type At<T extends readonly any[], I extends number> =
`${I}` extends `-${infer J}` ? [never, ...Reverse<T>] extends
infer R ? J extends keyof R ? R[J] :
undefined : never : T[I]
interface ReadonlyArray<T> {
at<I extends number>(index: I): At<this, I>;
}
interface Array<T> {
at<I extends number>(index: I): At<this, I>;
}
vilket fungerar för ditt exempel på kod:
function f(arg: T1 | T2 | T3): [string, number[]] {
const penultimate = arg.at(-2) // string
const ultimate = arg.at(-1) // number[]
return [penultimate, ultimate]; // okay
}
men är förmodligen mer besvär än det är värt, särskilt eftersom det utan tvekan finns många kantfall och du skulle behöva underhålla det själv. Om TS-teamet tycker att det är för komplext att tillhandahålla i det inbyggda TS-biblioteket, bör du förmodligen undvika att göra det för din egen kodbas om du inte har en mycket bra anledning.
Så det är för närvarande inte möjligt i TypeScript, åtminstone för tillfället. Du kan lika gärna använda typassertioner som arg[arg.length-2] as string och arg[arg.length-1] as number[] och gå vidare.
Lekplatslänk till kod
Svar #2
Eftersom unionstyper innehåller ett begränsat antal olika typer kan du begränsa den typen med hjälp av if-satser eller en switch-sats:
type T1 = [number, string, number[]];
type T2 = [number, number, string, number[]];
type T3 = [number, number, number, string, number[]];
function works(arg: T1 | T2 | T3): [string, number[]] {
switch (arg.length) {
case 3:
return [arg[1], arg[2]];
case 4:
return [arg[2], arg[3]];
case 5:
return [arg[3], arg[4]];
default:
throw new Error("Invalid tuple type");
}
}
Om du är villig att ändra din funktionsdeklaration kan du ändra parametertypen till [...any, string, number[]] för att definiera rest-element. T1, T2 och T3 ärvt alla den typen. På så sätt kan TypeScript härleda de sista elementen.
type T1 = [number, string, number[]];
type T2 = [number, number, string, number[]];
type T3 = [number, number, number, string, number[]];
function works2(arg: [...any, string, number[]]): [string, number[]] {
return [arg[arg.length - 2], arg[arg.length - 1]];
}
TypeScript-lekplats
