【问题标题】:how to take any constructor type and convert it to a function type which accepts the same arguments?如何获取任何构造函数类型并将其转换为接受相同参数的函数类型?
【发布时间】:2021-02-02 09:10:32
【问题描述】:

考虑下面的代码,我创建了一个类,它有一个构造函数,它接收一个泛型类型的参数,用于定义第二个参数接收到的参数的类型,在下面的例子中回调函数是MouseEvent,因为第一个参数的值:

class MyClass<T extends keyof HTMLElementEventMap>{
    constructor(tagName: T, lister: (ev: HTMLElementEventMap[T]) => any) { }
}

new MyClass("click", ev => { })
//                   ^^ (parameter) ev: MouseEvent

但是,如果我创建一个函数,其其余参数的类型为 ConstructorParameters,则第二个参数解析回调参数的 MouseEvent 类型,解析为 Event | UIEvent | AnimationEvent | MouseEvent | FocusEvent | DragEvent | ErrorEvent | PointerEvent | ... 6 more ... | ClipboardEvent

function myFunction(...args: ConstructorParameters<typeof MyClass>) {}

myFunction("click", ev => { })
//                  ^^ (parameter) ev: Event | UIEvent | AnimationEvent | MouseEvent | FocusEvent | DragEvent | ErrorEvent | PointerEvent | ... 6 more ... | ClipboardEvent

如何在不重写myFunction 函数的类型的情况下获得回调参数的正确类型?

有可能吗?

【问题讨论】:

  • Ehhh,没有高阶类型操作可以纯粹在类型级别执行此操作。你可以通过对higher order inference from generic functions 的一些轻微滥用来获得你想要的东西,比如this...这对你有用吗?如果是这样,我会写一个答案。
  • 哇!这是魔法吗?我不太明白它是如何工作的,但它确实有效,你能更好地解释一下吗?
  • 我注意到这会生成 JS 代码,这种方法不仅在“类型”领域,您能说一下为什么会这样吗?是否有任何解决方案不会生成更多的 JS 代码?
  • 正如我所说,我不知道有任何解决方案可以纯粹在类型级别上工作。它可能需要microsoft/TypeScript#1213 中请求的更高种类的类型,但这些不是语言的一部分。
  • 感谢您的帮助,请给出答案,以便我将其标记为有用。

标签: typescript arguments


【解决方案1】:

您想说的是“采用任何构造函数类型并将其转换为接受相同参数的 function 类型”。


一个问题是 TypeScript 的类型系统没有一种很好的方式来表达适用于 generic 函数的“可调用或可构造事物的参数”。如果你有这样的功能

function foo<T>(x: T, y: T): void { }

并尝试获取其参数列表,泛型参数T 将替换为其constraint。在这种情况下,T 不受约束,因此具有unknown 的隐式约束:

type FooParams = Parameters<typeof foo>;
// type FooParams = [x: unknown, y: unknown]

TypeScript 没有正确的泛型类型来表示泛型函数的参数列表。泛型函数在调用签名上有其泛型类型参数。但是像[x: unknown, y: unknown] 这样的元组类型没有调用签名,也不能接受泛型类型参数:

// Not valid TS, do not use this:
type FooParams = <T>[x: T, y: T];

为了表示这一点,TypeScript 需要像 microsoft/TypeScript#17574... 中要求的任意通用值类型之类的东西,但它没有这些。


好的,所以不用担心元组类型,也许我们可以自动将一种泛型函数类型转换为另一种。不幸的是,该语言没有正确的类型运算符来执行此操作。为了捕获泛型函数与其类型参数之间的关系,TypeScript 可能需要像 microsoft/TypeScript#1213 中所要求的“高级类型”之类的东西……但它也没有这些。


在 TypeScript 3.4 之前,我会说我们会完全陷入困境。但是 Typescript 3.4 引入了对 higher order type inference from generic functions 的支持。这是更高种类类型的部分实现,但没有可用的类型级语法来使用它。您可以将一个实际的泛型函数 value 转换为另一个泛型函数 value,其中输出函数的类型与输入函数的类型完全符合您的需要。如果您没有实际的函数值,那么您可以制作一个或假装制作一个,对该值进行高阶推理,然后获取输出函数的类型。但是,正如您所指出的,如果您只关心打字,那么这会在您的输出中添加一些您根本不需要的实际 JavaScript。对于MyClass 示例,它看起来像这样:

// abuse TS3.4 support for higher order inference from generic functions
const ctorArgs = <A extends any[], R>(f: new (...a: A) => R): (...a: A) => void => null!
const myFunc = ctorArgs(MyClass)
type MyFunctionType = typeof myFunc;

const myFunction: MyFunctionType = (...args) => {}
myFunction("click", ev => { ev.buttons })

您可以看到ctorArgs 正在执行您想要的类型操作,但在值级别。在上面的代码中,ctorArgs 丢弃了它的输入,只是假装返回一个函数;而我们又会丢弃输出并获取其类型。

由于您无法逃避其中的 JavaScript 部分,也许您实际上可以利用它来发挥您的优势并通用实现 myFunction。你会做什么来将构造函数转换为返回 void 的函数?在上面调用new 然后丢弃结果?像这样:

const makeMyFunction = <A extends any[], R>(
  f: new (...a: A) => R): (...a: A) => void =>
    (...a) => { new f(...a) } // whatever the implementation should do

const myFunction = makeMyFunction(MyClass);

在这种情况下,myFunction 会免费出现。但这是否适用取决于您的用例,我不知道。


Playground link to code

【讨论】:

    猜你喜欢
    • 2017-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    相关资源
    最近更新 更多