hur kan jag uttrycka att en parameter kan utökas till en lista med strängar i TypeScript?


Jag har en metod som tar en string-parameter. Men jag vill begränsa den parametern lite. Så jag skapar en unionstyp med de strängar jag vill acceptera:

type Foo = 'a' | 'b';

nu har jag en enkel metod:

function bar(foo: Foo) {
    // do something with that string
}

Vad jag vill göra är att tillåta en uppringare att skicka andra strängar (förutom a och b). Jag vill dock att de på ett sätt ska utöka den ursprungliga unionstypen.

function bar<T extends Foo>(str: T) {
    // do something with that string
}
type Bar = Foo | 'c';
// this doesn't work: Argument of type 'Bar' is not assignable to parameter of type 'Foo'. Type '"c"' is not assignable to type 'Foo'.
bar('c' as Bar); 

Finns det något sätt att uttrycka denna begränsning i TypeScript?

Svar #1

@jscalz kommentar fick mig att ompröva mitt svar.

Ur ett klassiskt OOP-perspektiv skulle extends-begränsningen i bar<T extends Foo>(arg: T) på den generiska parametern innebära att T har alla egenskaper hos Foo och mer. Så med denna begränsning kan du säkert anta i din bar att arg beter sig åtminstone som Foo (och har samma egenskapsstruktur).

Men i ditt fall är T varken en klass eller ett interface utan en union av strängliteraler och det är här extends blir meningslöst:

Låt oss anta att bar<T extends Foo>(arg: T) fungerade som du vill (som angivet i din OP), vilket betyder att T är en övermängd av strängliteraler ovanpå Foo. Detta skulle innebära att i din bar()-funktion skulle du inte ha någon kontroll alls över vilka värden arg innehåller - det kan innehålla vilken som helst typ av värden och du kan lika gärna använda any.

Givet det ovanstående är din enda chans att kontrollera vad som tar sig in i bar() att använda function bar(args: Bar), där Bar=Foo | 'c' vilket sedan också tillåter dig att kalla bar('c'); utan att casta.

Originalt svar:

Att definiera type Bar som en intersections-typ verkar åtminstone dämpa Typescript-linterfelen: type Bar=Foo & 'c';.

Nedan listar jag de relevanta delarna (åtminstone enligt min förståelse) från TypeScript-specifikationen som definierar att en intersection av typer faktiskt leder till en "Subtype-Supertype"-relation som erkänns av extends-begränsningen:

I 3.11.3 Undertyper och Överordnade typer står det:

S är en subtyp av en typ T, och T är en supertyp av S, om S inte har några överflödiga egenskaper med avseende på T (3.11.5) och en av följande är sann:

[...]

  • S är en unionstyp och varje enskild typ som ingår i S är en subtyp av T.

Dessutom i 3.11.4 Tilldelningskompatibilitet (fetstilat av mig):

S är tilldelningsbar till en typ T, och T är tilldelningsbar från S, om S inte har några överflödiga egenskaper i förhållande till T (3.11.5) och en av följande är sann:

[...]

  • S är en unionstyp och varje enskild typ som ingår i S kan tilldelas T.

När det gäller nyckelordet extends säger Typescript-handboken följande (gjort fet av mig):

För praktiska ändamål bestäms typkompatibilitet av tilldelningskompatibilitet, även i fall av implementerar- och utökar- klausuler.

I ditt fall motsvarar Foo S i specifikationen och Bar är din supertyp (eller superset) som motsvarar T i specifikationen.

Men, jag är inte säker på om detta svar tillfredsställer dig eftersom du också kan skriva bar('x' som Bar); och kompilatorn kommer inte att visa ett fel trots att 'x' inte ingår i dina typer.

Jag tycker att ditt bästa alternativ helt enkelt är att använda function bar(x: Bar). Vilket då också gör att du kan anropa bar('c');