为什么 Function 会在 TypeScript 中扩展 Record?

Laz*_*vić 2 static-typing typescript

const fn = () => null
type Answer = typeof fn extends Record<string, any> ? true : false
Run Code Online (Sandbox Code Playgroud)

上面的代码片段Answer是 type true,表明函数被认为是 的子类型Record。为什么?

如何使我的类型更严格,以便函数不是它的子类型?

jca*_*alz 5

因此,根据您的评论,您需要诸如“一个不是函数的对象”之类的东西。TypeScript 目前没有否定类型,因此没有简洁的方式来表达“非函数”。根据您的用例,您可能只想耸耸肩继续前进。

或者,您可能会尝试通过变通方法强制 TypeScript 屈从于您的意愿。


最简单的解决方法是找到一些函数始终具有的属性,并声明您的类型具有不兼容的同名可选属性。例如:

type NoCall = { call?: number | string | boolean | null | undefined, [k: string]: any };
const okay: NoCall = { a: 4 }; // okay
const err: NoCall = () => 2; // error
Run Code Online (Sandbox Code Playgroud)

一个缺点是您将无法验证某些合法的非函数(在上述情况下,{call: ()=>0}不是函数但无法验证为NoCall)。但是,对于您的用例,也许一些误报是可以的?

无论如何,根据标准库的合理选择lib.es5.d.ts的属性名称来检查是applycallbindarguments,和caller


如果您不喜欢放弃这些名称之一,您可以选择使用全局增强来向Function界面添加幻影属性:

declare global {
  interface Function {
    '*FunctionsHaveThis*': true
  }
}
type ObjNotFunction = { '*FunctionsHaveThis*'?: never, [k: string]: any };
const okay: ObjNotFunction = { a: 4 }; // okay
const err: ObjNotFunction = () => 2; // error
Run Code Online (Sandbox Code Playgroud)

这使得属性名称冲突的可能性较小,但会污染全局命名空间并使这种解决方法变得不那么简单。


另一个解决方法是使用一个特技条件类型,其防止作为参数传递给另一个函数被传递的函数:

function noFunctionsPlease<T extends object>(t: T & (T extends Function ? never : T)) {
  // do something with t
}
noFunctionsPlease({ a: 4 }); // okay
noFunctionsPlease(() => 2); // error
Run Code Online (Sandbox Code Playgroud)

的类型t或多或少准确地说是“一个不是函数的对象”,但它只能表示为函数参数。


希望其中之一可以帮助您或让您了​​解如何(或是否)继续。祝你好运!