假设我想要一个包装另一个函数的函数,目的是稍微调整一下参数:

type WrappedFunction<P> = (props: P, children?: string[]) => void
type NonWrappedFunction<P> = (props: P, children: string[]) => void

const wrapFunction = <P>(rawFn: NonWrappedFunction<P>): WrappedFunction<P> => {
    return (props, children) => rawFn(props, children ?? [])
}

然后,我可以将泛型函数传递给包装器函数,它也可以很好地工作:

const wrappedFunction = wrapFunction(<T>(props: {val: T}) => {})
// wrappedFunction is <T>(props: {val: T}, children?: string[]) => void , which is right

但是,如果我更改包装器函数的返回类型以添加不使用泛型参数P的重载,则会出现编译错误:

// if I change WrappedFunction to this
type WrappedFunction<P> = ((props: P, children?: string[]) => void) & ((children?: string[]) => void)
// then I'll have TypeScript error about inability to infer props type here:
const wrappedFunction = wrapFunction(<T>(props: {val: T}) => {})

即使对于更简单的类型,它也是这样工作的;例如,将包装器定义更改为

const wrapFunction = <P>(rawFn: NonWrappedFunction<P>): void => {

将导致完全相同的错误.

因此,TypeScrip使用包装函数的返回类型来推断泛型参数.但是为什么呢?如何避免呢?

我试着内联字体,希望它可能太复杂了,打字在某一点上会引发推理.我还发现,如果非包装函数的参数仅为T(而不是{val:T}),则在该位置不会发生错误(但仍然不会推断类型).

推荐答案

目前操作generic function types的唯一工具是支持higher order function inference,就像在microsoft/TypeScript#30215中实现的那样.这种支持仅适用于将泛型函数作为参数传递给更高阶泛型函数的非常特定的场景.特别是,它要求"被调用的函数是返回函数类型with a single call signature的泛型函数[已强调]".

因此,如果您将函数更改为返回

type WrappedFunction<P> = 
  ((props: P, children?: string[]) => void) & ((children?: string[]) => void)

它将中断,因为这是两个调用签名的交集,而不是单个调用签名.你不能返回一个overloaded function,然后得到更高阶的函数推断.


幸运的是,您可以将WrappedFunction<P>重新表述为单个调用签名.在概念上,像{ (...args: A1): R; (...args: A2): R; (...args: A3): T }这样的重载函数和像{ (...args: A1 | A2 | A3): T }这样的单个函数是等价的.如果您的所有调用签名都具有相同的返回类型,则可以将它们合并为一个调用签名,其值为uniontuple types as a rest parameter.

所以这就是

type WrappedFunction<P> = ((...args:
  [props: P, children?: string[]] |
  [children?: string[]]
) => void)

一旦您这样做了,高阶函数推理就会再次开始工作:

const wrappedFunction = wrapFunction(<T,>(props: { val: T }) => { })
/* const wrappedFunction: <T>(...args: 
     [children?: string[] | undefined] | 
     [props: { val: T; }, children?: string[] | undefined]
   ) => void */

这恰好管用,这很好.但如上所述,对高阶函数推理的支持仅适用于非常特定的场景,因此是fragile.future 发现自己需要支持标签外使用的读者应该注意这一点.对WrappedFunction<>的定义稍作改动是非常容易的,这将无可挽回地 destruct 它.如果要向函数添加属性,或使函数成为对象的方法,它可能会停止工作.

Playground link to code

Typescript相关问答推荐

替代语法/逻辑以避免TS变量被分配之前使用." "

在TypeScript中,除了映射类型之外还使用的`in`二进制运算符?

如何在TypeScript中将一个元组映射(转换)到另一个元组?

向NextAuth会话添加除名称、图像和ID之外的更多详细信息

如何在LucideAngular 添加自定义图标以及如何使用它们

MatTool提示在角垫工作台中不起作用

如果数组使用Reaction-Hook-Form更改,则重新呈现表单

Fp-ts使用任务的最佳方法包装选项

常量类型参数回退类型

如何在省略一个参数的情况下从函数类型中提取参数类型?

为什么我的函数arg得到类型为Never?

如何在具有嵌套数组的接口中允许新属性

设置不允许相同类型的多个参数的线性规则

如何在Angular /字体中访问API响应的子元素?

在REACT查询中获取未定义的isLoading态

两个名称不同的相同打字界面-如何使其干燥/避免重复?

仅当类型为联合类型时映射属性类型

TS 排除带点符号的键

为什么 Typescript 无法正确推断数组元素的类型?

如何让 Promise 将它调用的重载函数的类型转发给它自己的调用者?