如何定义具有重载的函数以恰好具有 1 或 2 个参数,这些参数的类型取决于使用的字符串文字

Posted

技术标签:

【中文标题】如何定义具有重载的函数以恰好具有 1 或 2 个参数,这些参数的类型取决于使用的字符串文字【英文标题】:How to define function with overloads to have exactly 1 or 2 arguments which types depend on used string literal 【发布时间】:2020-01-05 18:23:08 【问题描述】:

我想实现一个emitEvent(event, extra?) 函数,该函数将受已知字符串的字符串字面值枚举约束,例如POPUP_OPENPOPUP_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

总的来说,我担心这是不可能实现的,但希望我错了:)

【问题讨论】:

【参考方案1】:

您可以使用tuples in rest parameters 代替重载来让函数接受可变数量的参数。 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

play

【讨论】:

不错!也可以通过此实现删除重载,但是通过删除,我们将不得不手动从其余数组中仅选择第一项,这确实不是很好的解决方案...ts function emitEvent&lt;T extends KnownEvents, E extends GetExtras&lt;Events[T]&gt;&gt;(event:T, ...extra: E) const parsedEmit = parse(event) return ...parsedEmit,...extra[0] 【参考方案2】:

如何将您的第一个函数重载声明限制为不包含extra 属性的事件?

// define events, that do not have extra property
// "POPUP_CLOSED" | "AD_BLOCKER_OFF"
type KnownEventsWithoutExtra = 
  [K in keyof Events]: Events[K] extends  name: string; extra: object 
    ? never
    : K
[keyof Events];

// single argument overload is restricted to above events
function emitEvent<T extends KnownEventsWithoutExtra>(event: T): ParsedEvent;

// will error now
const t4 = emitEvent("POPUP_OPEN");

Playground

【讨论】:

我最初试图通过映射类型来做到这一点,但无法做到。有时我只是让我猜想的事情过于复杂:D 这绝对有效!尽管重载实现更通用,所以我将其标记为解决方案。谢谢!

以上是关于如何定义具有重载的函数以恰好具有 1 或 2 个参数,这些参数的类型取决于使用的字符串文字的主要内容,如果未能解决你的问题,请参考以下文章

java 重写 重载

重载与重写

重载与重写的区别

java重载和重写

java方法重载学习笔记

java重载和重写的区别