TypeScript中对函数应用Omit<T,K>等泛型后为何无法调用?
问题现象
在TypeScript中给可调用函数类型应用Omit<T, K>泛型工具类型后,函数会直接失去可调用能力,最小复现代码如下:
declare function myCallableFunction(): void; myCallableFunction(); // 类型校验通过 type NonOmittedFunction = typeof myCallableFunction; declare const myNonOmittedFunction: NonOmittedFunction; myNonOmittedFunction(); // 类型校验通过 type OmittedFunction = Omit<typeof myCallableFunction, 'foobar'>; declare const myOmittedFunction: OmittedFunction; myOmittedFunction(); // 类型报错:This expression is not callable. // Type 'OmittedFunction' has no call signatures.
根本原因
这个问题是TypeScript映射类型的固定设计行为导致的,没有特殊黑魔法:
- 函数在TypeScript里是特殊的对象类型,它能被调用,靠的是独立于常规字符串/数字/Symbol属性之外的调用签名(call signature),调用签名不属于普通对象属性的范畴。
Omit、Partial、Required、Readonly这些内置工具类型,底层全是靠映射类型实现的。拿Omit举例子,它的核心实现逻辑非常简单:
映射类型构造新类型的时候,只会遍历、复制原类型上的常规属性,根本不会管原类型上挂的调用签名、构造签名这类特殊成员。不管你Omit掉的属性和函数本身有没有关系,只要走了一遍映射类型的重映射流程,出来的新类型就是个纯普通对象,没有调用签名,自然不能当函数调用。type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; type Pick<T, K extends keyof T> = { [P in K]: T[P] };- 就算是给函数交叉了额外属性(比如常见的挂载了count属性的计数函数),过一遍映射类型照样会丢调用签名,典型场景代码如下:
declare type CallCountingFunction = (() => void) & { count: number } const myFunction: CallCountingFunction = (() => { const x = () => {}; x.count = 0; return x; })() myFunction.count; // 校验通过 myFunction() // 校验通过 type OmittedFunction = Omit<CallCountingFunction, 'count'>; declare const myOmittedFunction: OmittedFunction; myOmittedFunction(); // 报错:This expression is not callable. // Type 'OmittedFunction' has no call signatures. - 实际测试下来,只要是走类型重映射逻辑的泛型工具类型,全都会有这个问题,不是
Omit独有的bug。
内容的提问来源于stack exchange,提问作者Slava Knyazev
相关产品推荐
相关产品推荐

