typescript - Varför fungerar inte villkorliga typer i generiska funktioner?


Varför kan inte TypeScript använda begränsningarna på typparametrarna för en generisk funktion för att härleda typerna på parametrarna eller returtypen?

function isNum<T extends number>(x:T):T extends number?”Y”:”N”{
    return “Y”; // <- error: type “Y” is not assignable to type ‘T extends number?”Y”:”N”’
}

Finns det något sätt att uppnå önskat resultat (förutom att forka TS)?

(använder TS 2.9.2)

Svar #1

Att tilldela värden där en generisk typ förväntas fungerar generellt inte, TypeScript antar att värdet inte kommer att vara kompatibelt. Detta beror på att när vi har en generisk typ representerar den typen vilken som helst möjlig subtyp av T.

Nu i det här fallet är det ganska uppenbart att T alltid kommer att vara "Y" eftersom det redan finns en begränsning på T, men TypeScript kommer helt enkelt inte att försöka härleda detta. Ett ännu mer uppenbart fall kommer inte att fungera, eftersom TypeScript helt enkelt inte kommer att göra någon analys i det här fallet:

function isNum<T extends number>(x: T): T extends number ? "Y" : "Y" {
    return "Y"; // still an error 
}

Den enda lösningen är att använda en typ påstående:

function isNum<T extends number>(x: T): T extends number ? "Y" : "N" {
    return "Y" as any;
}

Redigera

Som @jcalz noterade är ett annat mycket bra alternativ att använda flera överlagringar (eller mer specifikt en offentlig överlagring som använder villkorliga typer, och en implementeringsversion som inte använder villkorliga typer)

function isNum<T extends number>(x: T): T extends number ? "Y" : "N"
function isNum(x: number): "Y" | "N" {
    return "Y";
}

Svar #2

Nej, Typescript stöder ännu inte villkorliga returtyper. När det gäller ditt fall kan du göra följande några saker:

Returtyp som ett Enum

enum YesNo {
  Y= 'Y',
  N= 'N'
}
function isNum<T extends Number>(x:T) : YesNo {
  return x as any instanceof Number ? YesNo.Y : YesNo.N;
}

Strikt returtyp

function isNum<T extends Number>(x:T) : 'Y' | 'N' {
  return x as any instanceof Number ? 'Y' : 'N';
}

Om du vill ha returtyp som specifika typer

interface ANumber extends Number {
  ...
}
interface BNumber extends Number {
  ...
}
function isNum<T extends Number>(x:T) : ANumber | BNumber {
  return x;
}

Obs: Ovanstående gränssnitt kan också vara klasser.