typescript - 如何定义具有重载的函数以恰好具有 1 或 2 个参数,这些参数的类型取决于使用的字符串文字
问题描述
我想实现一个emitEvent(event, extra?)
函数,该函数将受已知字符串的字符串文字枚举约束,例如POPUP_OPEN
等POPUP_CLOSED
。这个函数接受第二个参数,它又是一个明确定义的字典形状,只能与特定的事件键一起使用。
这是所有已知事件的字典:
interface Events {
POPUP_OPEN: {name:'POPUP_OPEN',extra: {count:number}},
POPUP_CLOSED: {name:'POPUP_CLOSED'},
AD_BLOCKER_ON: {name:'AD_BLOCKER_ON', extra: {serviceName:string}},
AD_BLOCKER_OFF: {name:'AD_BLOCKER_OFF'}
}
具有所需类型约束的用法:
// $ExpectType {object: string; action: string; value: string;}
const t1 = emitEvent('POPUP_CLOSED')
// $ExpectError -> no extra argument allowed
const t11 = emitEvent('POPUP_CLOSED', {what:'bad'})
// $ExpectType {object: string;action: string;foo: string; count: number;}
const t2 = emitEvent('POPUP_OPEN',{count: 1231})
// $ExpectError -> extra argument is missing
const t22 = emitEvent('POPUP_OPEN')
我的实现:
这个实现有一个大问题,对于已经定义的字典值,extra
没有定义的时候TS不会抱怨
// ✅ NO ERROR
const t2 = emitEvent('POPUP_OPEN',{count: 1231})
// ✅ Error
const t2 = emitEvent('POPUP_OPEN',{})
// NO ERROR -> THIS SHOULD ERROR !
const t2 = emitEvent('POPUP_OPEN')
执行:
type ParsedEvent<Extra = void> = Extra extends object ? BaseParsedEvents & Extra : BaseParsedEvents
type BaseParsedEvents = {
object:string
action:string
value:string
}
type KnownEvents = keyof Events
type GetExtras<T> = T extends {name: infer N, extra: infer E} ? E : never;
function emitEvent<T extends KnownEvents>(event:T): ParsedEvent
function emitEvent<T extends KnownEvents, E extends GetExtras<Events[T]>>(event:T, extra: E): ParsedEvent<E>
function emitEvent<T extends string, E extends object>(event:T, extra?: E) {
const parsedEmit = parse(event)
return {...parsedEmit,...extra}
}
function parse(event:string): BaseParsedEvents {
const [object,action,value] = event.split('_')
return {object,action,value}
}
总的来说,我担心这是不可能实现的,但希望我错了:)
解决方案
您可以在剩余参数中使用元组而不是重载来让函数接受可变数量的参数。GetExtras
将返回具有单个E
元素的元组或空元组。然后我们可以展开GetExtras
作为函数的展开参数:
interface Events {
POPUP_OPEN: {name:'POPUP_OPEN',extra: {count:number}},
POPUP_CLOSED: {name:'POPUP_CLOSED'},
AD_BLOCKER_ON: {name:'AD_BLOCKER_ON', extra: {serviceName:string}},
AD_BLOCKER_OFF: {name:'AD_BLOCKER_OFF'}
}
// $ExpectType {object: string; action: string; value: string;}
const t1 = emitEvent('POPUP_CLOSED')
// ✅ NO ERROR
const t21 = emitEvent('POPUP_OPEN',{count: 1231})
// ✅ Error
const t213 = emitEvent('POPUP_OPEN',{})
// ✅ ERROR as expected
const t223 = emitEvent('POPUP_OPEN')
type ParsedEvent<Extra extends [object] | [] = []> = Extra extends [infer E] ? BaseParsedEvents & E : BaseParsedEvents
type BaseParsedEvents = {
object:string
action:string
value:string
}
type KnownEvents = keyof Events
type GetExtras<T> = T extends {name: infer N, extra: infer E} ? [E] : [];
function emitEvent<T extends KnownEvents, E extends GetExtras<Events[T]>>(event:T, ...extra: E): ParsedEvent<E>
function emitEvent<T extends string, E extends object>(event:T, extra?: E) {
const parsedEmit = parse(event)
return {...parsedEmit,...extra}
}
function parse(event:string): BaseParsedEvents {
const [object,action,value] = event.split('_')
return {object,action,value}
}
推荐阅读
- c# - 如何配置代理以便它可以在 Web 上运行测试用例时使用特定用户
- java - RxJava 2:为什么 PublishProcessor 不能订阅 Observable?
- python-3.x - KIVY - 从 Python 更改画布的颜色
- typescript - 如何解决这个 Coroutine 循环引用错误?
- php - 关于在类中调用 PHP 函数的一些解释
- javascript - 无法将值传递给父组件反应
- java - 访问 cumulocity maven 存储库
- c# - SSIS C# 客户端发送 SOAP 请求和接收响应
- vb.net - 每个 .Net 项目有多少个 VB.net System.Timers.Timers?
- excel - 如何在工作表中搜索现有的命令按钮?