TypeScript-generiske typer, begrensninger og litterale typer


Jeg har følgende generiske funksjon:

type Identity = <T extends string | number>(arg: T) => T;
const identity: Identity = (x) => x;

Hvis jeg prøver å bruke denne funksjonen på denne måten, får jeg feil om at jeg ikke kan tilordne på nytt.

let a = identity('a');
a = 'b'; // Error: Type '"b"' is not assignable to type '"a"'

let b = identity(1);
b = 2; // Error: Type '2' is not assignable to type '1'

Jeg ville forvente at typen som returneres fra identity enten er en string eller number, men det ser ut til at jeg får tilbake literale typer av 'a' og 1. Hvorfor er dette tilfelle? Og er det en måte å endre begrensningene mine på slik at dette blir adferden?

Jeg har funnet et par løsninger. Først å tildele parameteren til en variabel først:

let a_param = 'a'
let a = identity(a_param);
a = 'b'; // Yay

let b_param = 1;
let b = identity(b_param);
b = 2; // Yay

Og for det andre å kaste til en string eller number:

let a = identity('a' as string);
a = 'b'; // Yay

let b = identity(1 as number);
b = 2; // Yay

Begge disse føles feil. Jeg holder fortsatt på å lære TS, så jeg tror jeg har gått glipp av noe.

Svar #1

Typeparameteren vil normalt bli utledet som den mest spesifikke typen som er konsistent med argumentet; i dette tilfellet strenglitteraltypen 'a'. Dette er nesten alltid ønsket oppførsel.

I ditt tilfelle er den utledede typen for streng fordi du bruker den som den utledede typen til en variabel, ikke bare som den utledede typen til et uttrykk. Så en naturlig løsning er å spesifisere en svakere typeparameter, slik:

let a = identity<string>('a');

Det ville imidlertid være mer normalt å ganske enkelt spesifisere typen til variabelen:

let a: string = identity('a');

Svar #2

Du bruker en generisk, men kravene dine er motsetningsfulle til generiske typer. Dette er fordi identity('a') egentlig betyr identity<'a'>('a'), derfor er returtypen 'a' (ikke hvilken som helst streng).

Det som fungerer (av en eller annen grunn) er:

type Identity = {
  (arg: number): number;
  (arg: string): string;
}

const identity: Identity = (x: any) => x;

let a = identity('a');
a = 'b'; // yes
a = 3; // no

let b = identity(1);
b = 'b'; // no
b = 3; // yes

Jeg tror hvordan dette fungerer er at det avgjør hvilken av overlasterne som passer best innenfor typen Identity når du kaller funksjonen som implementerer den typen.

Lekerom lenke

Relaterte artikler