typescript - Avgränsa medlems typ baserat på en annan medlem
type Event<S extends string> = {
name: S
}
const EventHandler = <S extends string, T extends Event<S>[]>(arr: [...T]) => {
let lastEvent: T[number] = arr[0]
return {
set: (event: T[number]) => lastEvent = event,
emit: (event: typeof lastEvent['name']) => { },
lastEvent
}
}
const emitter = EventHandler([{ name: 'A' }, { name: 'B' }])
if(emitter.lastEvent.name === 'A') {
// `emitter.lastEvent` is now inferred to `{ name: A }`
emitter.emit('B') // compiles, but I want it to only allow the string `A`
}
I detta påhittade exempel skapar jag ett objekt EventHandler som har någon konfiguration lastEvent. Nu vill jag att parametern för emit ska begränsas beroende på konfigurationen.
Men även om typen av lastEvent begränsas i villkoret, kan jag inte begränsa inputen till emit som ett resultat av det.
Är detta möjligt? Helst utan att använda användardefinierade typvakter...
[REDIGERA]:
Jag trodde att jag kunde göra ett kompromiss och lägga till händelseobjektet som indata till emit:
emit: (event: T[number], name: typeof event['name']) => { }
och gör sedan:
emitter.emit({ name: 'A' }, 'B') // why does this compile ?!
men det här härleds inte heller! Detta verkar märkligt - varför kan inte TypeScript hantera detta?
Svar #1
För att lösa detta skulle vi idealiskt sett bygga en diskriminerad union så att typen av emit-funktionen kan avgränsas genom att kontrollera typen av lastEvent. Detta skulle kräva att vi kontrollerar det nästlade värdet av emitter.lastEvent.name. Men det är ett problem eftersom TypeScript för närvarande inte tillåter avgränsning av diskriminerade unioner baserat på djupt nästlade egenskaper (se #18758).
Det vi kan göra för tillfället är att undvika infogning och ha strängvärdet av name på objektets översta nivå.
Vi kan använda unionen av T[number] med en mappad typ för att bygga en diskriminerad union där varje unionkomponent K är diskriminanten.
const EventHandler = <T extends string[]>(arr: [...T]) => {
let lastEvent: T[number] = arr[0]
return {
set: (event: T[number]) => lastEvent = event,
emit: (event: typeof lastEvent) => { },
lastEvent
} as {
set: (event: T[number]) => void
} & {
[K in T[number]]: {
emit: (event: K) => void
lastEvent: K
}
}[T[number]]
}
Detta låter oss korrekt begränsa typen av emit genom att kontrollera värdet av lastEvent.
const emitter = EventHandler(['A', 'B'])
if(emitter.lastEvent === 'A') {
emitter.emit('A')
emitter.emit('B') // Error: Argument of type '"B"' is not assignable
// to parameter of type '"A"'
}
if(emitter.lastEvent === 'B') {
emitter.emit('A') // Error: Argument of type '"A"' is not assignable
// to parameter of type '"B"'
emitter.emit('B')
}
emitter.set('A')
emitter.set('B')
Lekplats
