typescript - 基于另一个成员来缩小成员类型
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`
}
在这个人为的例子中,我创建了一个对象 EventHandler,它有一些配置 lastEvent。现在,我想根据配置来缩小 emit 的参数范围。
然而,即使在条件语句中 lastEvent 的类型被缩小,我仍然无法因此缩小 emit 的输入类型。
这是可能的吗?最好不要使用用户定义的类型保护...
[编辑]:
我以为我可以做出妥协,并将事件对象作为输入添加到 emit 中:
emit: (event: T[number], name: typeof event['name']) => { }
然后做:
emitter.emit({ name: 'A' }, 'B') // why does this compile ?!
但这也无法被推断!这看起来很奇怪——为什么 TypeScript 无法处理这个?
答案#1
为了解决这个问题,我们理想情况下会构建一个区分联合类型,这样通过检查 lastEvent 的类型就可以缩小 emit 函数的类型。这将要求我们检查 emitter.lastEvent.name 的嵌套值。但这是一个问题,因为 TypeScript 目前不允许基于深层嵌套属性来缩小区分联合类型(参见 #18758)。
我们现在可以做的是避免嵌套,并将name的字符串值放在对象的顶层。
我们可以使用 T[number] 的联合类型与映射类型来构建一个判别联合,其中每个联合成员 K 是判别标志。
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]]
}
这让我们能够通过检查 lastEvent 的值来正确地缩小 emit 的类型。
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')
操场
