TypeScript中安全调用Type | (() => Type)类型参数函数的方法
问题描述
我想要定义一个泛型函数,参数可以是Type类型的值,或者是返回Type类型值的函数,示例代码如下:
function f<Type>(x: Type | (() => Type)): Type { return typeof x === 'function' ? x() : x; }
但TypeScript抛出以下错误:
This expression is not callable. Not all constituents of type '(() => Type) | (Type & Function)' are callable. Type 'Type & Function' has no call signatures.ts(2349)
原本期望TypeScript能通过typeof条件判断识别出x是函数,尝试用类型断言return typeof x === 'function' ? (x as () => Type)() : x;抑制了错误,但想了解更优雅的解决方法,以及该断言方案的潜在问题。
报错原因
TypeScript无法确定泛型Type是否排除了Function类型。当Type可能是函数类型时,联合类型Type | (() => Type)会被解析为(() => Type) | (Type & Function)——后者代表同时属于Type和Function的类型,这类类型可能没有明确的调用签名(比如带有额外属性的函数对象),因此TypeScript无法安全地判定它可被调用。
优雅的解决方法
方法1:使用类型谓词精准缩窄类型
定义一个辅助类型谓词函数,明确告诉TypeScript满足条件时x的类型:
function isFactory<Type>(x: Type | (() => Type)): x is () => Type { return typeof x === 'function'; } function f<Type>(x: Type | (() => Type)): Type { return isFactory(x) ? x() : x; }
类型谓词x is () => Type会让TypeScript在isFactory(x)为真时,准确将x的类型缩窄为() => Type,消除调用歧义。
方法2:约束泛型为非函数类型
如果业务场景中Type永远不会是函数类型,可以给泛型添加约束:
type NonFunction = Exclude<unknown, Function>; function f<Type extends NonFunction>(x: Type | (() => Type)): Type { return typeof x === 'function' ? x() : x; }
通过Type extends NonFunction限制Type只能是非函数类型,此时联合类型不会出现冲突的函数子类型,typeof x === 'function'就能正常缩窄类型。
类型断言方案的潜在问题
- 类型不匹配风险:如果
Type本身是函数类型(比如Type = () => number),参数x可能是Type类型的函数(即() => number),也可能是() => Type类型的函数(即() => () => number)。断言会直接调用所有函数类型的x,但前者调用后返回的是number,而函数的返回值要求是Type(即() => number),这会导致类型不匹配,运行时可能引发错误。 - 隐藏类型错误:断言跳过了TypeScript的类型检查,后续如果修改函数签名或业务逻辑,断言可能会掩盖真实的类型问题,增加维护成本。
内容的提问来源于stack exchange,提问作者Andreas Riedmüller

