Algebraiske datatyper i TypeScript
Formulert på en annen måte:
Hvordan ville du skrevet windowState-DOM-egenskapen i TypeScript?
LØST (i TypeScript 2):
declare var windowState: WindowState
const enum WindowState {
STATE_MAXIMIZED = 1,
STATE_MINIMIZED = 2,
STATE_NORMAL = 3,
STATE_FULLSCREEN = 4
}
...
var windowState = 5 // Type Error, as expected!
Originalspørsmål:
Hvordan kan jeg deklarere en type i TypeScript slik at den beskriver en algebraisk datatyper? Hensikten med dette er å beskrive et eksisterende API.
Når jeg prøver følgende, klager TypeScript åpenbart over at en type forventes:
type Weather = 'sunny' | 'bad'
En idé jeg hadde er å bruke en JavaScript 2015 Symbol, men det virker som TypeScript ikke kjenner til disse.
En annen idé var å bruke en enum, men TypeScript klager på at en medlemsinitialisator må være et konstant uttrykk:
const enum Weather {
sunny = 'sunny',
bad = 'bad',
windy = Symbol('windy')
}
Jeg ville ha trodd at en string-konstant er et konstant uttrykk.
Svar #1
TypeScript 2.0 har støtte for diskriminerte unions/algbriske datatyper. Dokumentasjonen er her.
Du kan kombinere strengliteraltyper, unionstyper, typevaktsmekanismer og typealiaser for å bygge et avansert mønster kalt diskriminert union, også kjent som merket union eller algebraiske datatyper. Diskriminerte unioner er nyttige innen funksjonell programmering. Noen språk diskriminerer unioner automatisk for deg; TypeScript bygger derimot på JavaScript-mønstre slik de eksisterer i dag. Det er tre ingredienser:
- Typer som har en felles litteral (eller enum) egenskap — diskriminanten.
- Et typealias som tar unionen av disse typene — unionen.
- Typebeskyttere på den vanlige egenskapen.
La oss begynne:
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
Først erklærer vi grensesnittene vi vil slå sammen. Hvert grensesnitt har en
kind-egenskap med en forskjellig strengliteraltype.kind-egenskapen kalles diskriminanten eller taggen. De andre egenskapene er spesifikke for hvert grensesnitt. Legg merke til at grensesnittene foreløpig er urelaterte. La oss sette dem sammen i en union:
type Shape = Square | Rectangle | Circle;
La oss nå bruke den diskriminerte unionen:
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
I hver av disse grenene vil TypeScript innsnevre typen. Hvis du prøver å bruke en case-setning med en verdi som ikke er til stede som noen kind-egenskap, vil TypeScript gi en feil.
Svar #2
For å bruke enums ville du skrevet det omtrent slik:
enum Weather {
Sunny,
Windy
};
let currently = Weather.Sunny;
Strenglitteraler
Den andre måten OP ber om at dette skal løses på kommer i TypeScript 1.8.
De ville la deg gjøre noe slik som dette:
type Weather = 'Sunny' | 'Windy';
