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');