为什么 Function 扩展了 TypeScript 中的 Record?
Why does Function extend Record in TypeScript?
const fn = () => null
type Answer = typeof fn extends Record<string, any> ? true : false
上面的代码片段将 Answer
生成为 true
类型,表明函数被视为 Record
的子类型。为什么?
如何使我的类型更严格,以便函数不是它的子类型?
Record<string, any>
表示具有任何字符串键和任何值的任何对象。所有的函数都是对象,它们都有一些属性(我们甚至可以索引到对象fn['call']
)。根据这些定义,并且由于 Typescript 中的类型关系基于结构,typeof fn
确实扩展了 Record<string, any>
所以,根据您的评论,您想要 "an object which is not a function" 这样的东西。 TypeScript 目前没有negated types,所以没有简洁的表达方式"not a function"。根据您的用例,您可能只想耸耸肩继续前进。
或者,您可以尝试通过变通方法强制 TypeScript 屈服于您的意愿。
最简单的解决方法是找到一些函数始终具有的 属性 并声明您的类型具有不兼容的同名可选 属性。例如:
type NoCall = { call?: number | string | boolean | null | undefined, [k: string]: any };
const okay: NoCall = { a: 4 }; // okay
const err: NoCall = () => 2; // error
缺点是您将失去验证某些合法非函数的能力(在上述情况下,{call: ()=>0}
不是函数,但无法验证为 NoCall
) .但也许一些误报对您的用例来说没问题?
无论如何,根据标准库 lib.es5.d.ts
对于 属性 要检查的名称的合理选择是 apply
、call
、bind
、arguments
,以及 caller
。
如果您不想放弃其中一个名称,您可以选择使用 global augmentation 将幻像 属性 添加到 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
这使得 属性 名称冲突的可能性降低,但会污染全局命名空间并使此解决方法变得不那么简单。
另一种解决方法是使用 trick with conditional types 来防止函数作为参数传递给另一个函数:
function noFunctionsPlease<T extends object>(t: T & (T extends Function ? never : T)) {
// do something with t
}
noFunctionsPlease({ a: 4 }); // okay
noFunctionsPlease(() => 2); // error
t
的类型差不多就是"an object that isn't a function",但是只能表示为函数参数
希望其中之一可以帮助您或让您了解如何(或是否)继续。祝你好运!
const fn = () => null
type Answer = typeof fn extends Record<string, any> ? true : false
上面的代码片段将 Answer
生成为 true
类型,表明函数被视为 Record
的子类型。为什么?
如何使我的类型更严格,以便函数不是它的子类型?
Record<string, any>
表示具有任何字符串键和任何值的任何对象。所有的函数都是对象,它们都有一些属性(我们甚至可以索引到对象fn['call']
)。根据这些定义,并且由于 Typescript 中的类型关系基于结构,typeof fn
确实扩展了 Record<string, any>
所以,根据您的评论,您想要 "an object which is not a function" 这样的东西。 TypeScript 目前没有negated types,所以没有简洁的表达方式"not a function"。根据您的用例,您可能只想耸耸肩继续前进。
或者,您可以尝试通过变通方法强制 TypeScript 屈服于您的意愿。
最简单的解决方法是找到一些函数始终具有的 属性 并声明您的类型具有不兼容的同名可选 属性。例如:
type NoCall = { call?: number | string | boolean | null | undefined, [k: string]: any };
const okay: NoCall = { a: 4 }; // okay
const err: NoCall = () => 2; // error
缺点是您将失去验证某些合法非函数的能力(在上述情况下,{call: ()=>0}
不是函数,但无法验证为 NoCall
) .但也许一些误报对您的用例来说没问题?
无论如何,根据标准库 lib.es5.d.ts
对于 属性 要检查的名称的合理选择是 apply
、call
、bind
、arguments
,以及 caller
。
如果您不想放弃其中一个名称,您可以选择使用 global augmentation 将幻像 属性 添加到 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
这使得 属性 名称冲突的可能性降低,但会污染全局命名空间并使此解决方法变得不那么简单。
另一种解决方法是使用 trick with conditional types 来防止函数作为参数传递给另一个函数:
function noFunctionsPlease<T extends object>(t: T & (T extends Function ? never : T)) {
// do something with t
}
noFunctionsPlease({ a: 4 }); // okay
noFunctionsPlease(() => 2); // error
t
的类型差不多就是"an object that isn't a function",但是只能表示为函数参数
希望其中之一可以帮助您或让您了解如何(或是否)继续。祝你好运!