Typescript泛型-将函数参数和返回类型限制为相同类型,并根据使用情况推断类型

时间:2019-05-08 20:00:32

标签: typescript generics

我试图限制函数参数以扩展其中参数和返回值限制为同一类型的函数,而不要求函数本身是通用的。

我有一个简单的工厂函数,该函数创建具有属性的dom节点,如下所示:

function div(className, attributes, children)

EventListener属性(例如onclick)仅限于函数或元组:

type EventHandler<T extends Event> = ((ev: T) => void) | [(ev: T, seed: SameType) => SameType, SameType];

SameType的基本注释之处在于,您应该提供一个元组,其中第一项是一个接受种子值的函数,第二个参数指定调用该函数后传递给该函数的初始种子。

我试图使参数本身成为通用参数,将自身传递给类型,然后使用条件匹配,但是没有运气。我不能在接口本身上使用泛型,因为那没有意义(onclick,oninput和onmouseover都将具有不同的SameType种子值)。

该函数的灵感来自Inferno的linkEvent函数(https://github.com/infernojs/inferno/blob/master/packages/inferno/src/core/types.ts#L45),但您不必传递一个函数,而只是传入一个我认为很方便的元组,但是输入它却是一场噩梦!感谢您的任何投入。

此问题是否与Typescript在值级别上禁止泛型有关,还是我缺少某些东西?我在考虑this issue

1 个答案:

答案 0 :(得分:1)

我会坚持使用每个处理程序上的函数。确保div函数的on*属性具有一个元组,其中两个元素以您想要的方式关联是可能的,但不是直接的方式。

这将涉及捕获传递到div函数的对象文字的实际类型,并使用条件类型检查此自定义约束:

function div<TAttr extends {
  onClick:EventHandler<MouseEvent>,
  onKeyDown:EventHandler<KeyboardEvent>
}>(attributes: TAttr & CheckAttr<TAttr>): TAttr {
  return attributes;
}

type UnionToIntersection<U> = 
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type CheckAttr<T> = UnionToIntersection<CheckAttrHelper<T>[keyof T]> 
type CheckAttrHelper<T> = {
  [K in keyof T]:
    T[K] extends [(ev: Event, seed: infer P) => infer R, infer S] ?
      ([R] extends [P] ? [S] extends [P] ? never
          : Record<K, "Seed type is not assignable to parameter type">
          : Record<K, "Return type is not assignable to parameter type">
      ): never
}

type EventHandler<T extends Event> = ((ev: T) => void) | [(ev: T, seed: any) => any, any];

//ok
let a = div({
  onClick: [(ev, n: string | number) => 2, "1"],
  onKeyDown: [(ev, n: number) => n + 1, 1]
});

//err 
// Type '(string | ((ev: MouseEvent, n: string) => number))[]' is not assignable to type 
//'[(ev: MouseEvent, n: string) => number, string] & 
// "Return type is not assignable to parameter type"'.
let a2 = div({
  onClick: [(ev, n: string) => 2, "1"],
  onKeyDown: [(ev, n: number) => n + 1, 1]
});