javascript - Hur man gör en elegant tillståndsövergångskarta i Typescript?
Om du kan komma på en bättre titel, var vänlig redigera!
Vad jag har är två enums, ungefär så här:
enum State {
A = "A",
B = "B",
C = "C"
}
enum Event {
X = "X",
Y = "Y",
Z = "Z"
}
Och jag vill deklarera en karta som säger "Om det aktuella tillståndet är S och händelsen är E så är det nya tillståndet S1".
Inte varje kombination av tillstånd och händelse kommer att ge ett nytt tillstånd, ibland gör händelsen ingenting.
Så, i pseudo-javascript vill jag skriva något som:
const transitions = {
State.A: {
Event.X: State.B,
Event.Y: State.C
},
State.B: {
Event.X: State.A
Event.Z: State.C
}
}
Tyvärr kan jag inte hitta ett sätt att skriva detta på ett elegant sätt. Objektliteraler kan inte ha icke-sträng-litteralnycklar (inte ens interpolerade) och indexertyper i Typescript kan inte vara unionstyper och… jag sitter fast. Allt jag kan komma på som fungerar är mycket mer omständligt och fult.
Svar #1
Ditt svar fungerar bra, men jag märkte att det finns ett litet problem med det.
Eftersom du använde ett generiskt string-index i StateMap innebär detta att du förlorar all typkontroll när du får åtkomst till StateMap.
Till exempel:
const stateTransitions: StateMap = {
[State.A]: {
[Event.B]: State.C
}
};
const x = stateTransitions.whatever // x is { [index: string]: State }
Vi borde kunna veta att whatever aldrig kan existera på en StateMap.
Här är ett alternativ:
type StateMap = Record<State, Record<Event, State>>
Nackdelen med ovanstående är att det tvingar dig att skriva ut alla kombinationer av tillstånd/händelser för att kunna kompilera. Så du skulle behöva skriva något som:
const stateTransitions: StateMap = {
[State.A]: {
[Event.X]: State.C,
[Event.Y]: State.C,
[Event.Z]: State.C
},
[State.B]: {
[Event.X]: State.C,
[Event.Y]: State.C,
[Event.Z]: State.C
},
[State.C]: {
[Event.X]: State.C,
[Event.Y]: State.C,
[Event.Z]: State.C
}
};
Det beror lite på hur du förväntar dig att implementera statskartan. Ska den inkludera varje stat som en övergripande nyckel? Om så är fallet är Record<State, ...> en bra utgångspunkt.
Ett av dina krav var:
Inte varje kombination av tillstånd och händelse kommer att ge ett nytt tillstånd, ibland gör händelsen ingenting.
Du kan täcka detta uttryckligen genom att ange att händelsen resulterar i samma tillstånd. Till exempel, om tillstånd A inte övergår när händelse B inträffar, då:
const stateTransitions: StateMap = {
[State.A]: {
[Event.B]: State.A
// ...
},
// ...
}
Redigera: Följande utifrån kommentarerna nedan på detta svar, om du vill utesluta vissa stater från statskartan eftersom de till exempel är slutliga stater, kan du göra det på följande sätt:
type StatesToInclude = Exclude<State, State.C | State.B>
type StateMap = Record<StatesToInclude, Record<Event, State>>
// ...
Svar #2
Skitsamma, jag hittade svaret själv 3 minuter senare. :D Det finns "beräknade egenskapsnamn". Så...
type StateMap = { [index: string]: { [index: string]: State } }
const stateTransitions: StateMap = {
[State.A]: {
[Event.B]: State.C
}
};
Hakparenteserna är acceptabla och stör egentligen inte läsbarheten, tycker jag. Tack allihopa för att ni har rubberduckat! :)
