如何在TypeScript中泛型约束高阶函数接收的函数参数?
约束函数参数为object子类型并自动推断返回类型的实现方案
需求与问题
需要实现一个函数createObjectAcceptingFunctions,接收一组函数作为参数,需满足:
- 每个传入函数的参数必须是
object的子类型; - 传入函数的具体类型(包括参数和返回值)能被完整推断,比如传入
[(x: {a:1}) => void]时,返回的类型要与该输入类型完全一致。
尝试实现时遇到逆变问题:若直接指定函数接收(input: object) => void的子类型,object会被视为逆变约束,导致仅允许object的超类型(如unknown),与需求中允许子类型的要求相反。
以下是最初的尝试代码:
type InputFunction<$I> = <$Input extends $I>(input: $Input) => void declare const createObjectAccepingFunctions: <const $Fs extends InputFunction<object>[]>(fs: $Fs) => { items: $Fs } const result = createObjectAccepingFunctions([ (_: { a: 1 }) => {}, // 应当通过校验 (_: string) => {}, // 应当校验失败 ]) result.items
解决方案
核心思路是利用协变类型约束,明确每个传入函数的参数类型必须是object的子类型,同时保留函数的具体类型信息以实现自动推断。
方案一:明确泛型函数参数约束
// 定义参数为object子类型的函数类型 type ObjectParamFunction<T extends object> = (input: T) => unknown declare const createObjectAcceptingFunctions: <const Fs extends ObjectParamFunction<any>[]>( fs: Fs extends ObjectParamFunction<infer T>[] ? T extends object ? Fs : never : never ) => { items: Fs } // 合法测试:参数均为object子类型,类型完整推断 const validResult = createObjectAcceptingFunctions([ (_: { a: 1 }) => {}, (_: { b: string }) => 42, ]) // validResult.items 类型为:readonly [(_: { a: 1; }) => void, (_: { b: string; }) => number] // 非法测试:参数为非object类型,触发类型错误 const invalidResult = createObjectAcceptingFunctions([ (_: string) => {}, // 错误:类型'string'不满足约束'object' ])
方案二:简洁的推断与约束写法(TS 4.7+)
declare const createObjectAcceptingFunctions: <const Fs extends readonly ((input: infer T) => unknown)[]>( fs: T extends object ? Fs : never ) => { items: Fs } // 测试效果与方案一完全一致 const validResult = createObjectAcceptingFunctions([ (_: { id: number }) => "ok", ]) // validResult.items 类型为:readonly [(_: { id: number; }) => string] const invalidResult = createObjectAcceptingFunctions([ (_: number) => {}, // 错误:类型'number'不满足约束'object' ])
原理说明
- 通过
const泛型参数保留数组中每个函数的具体字面量类型,确保返回类型与输入完全一致; - 利用条件类型
T extends object ? Fs : never校验每个函数的参数类型T是否为object的子类型,非object类型会触发never,从而抛出类型错误; - 避开了逆变约束的问题,直接对函数的参数类型进行协变校验,符合需求中允许
object子类型的要求。
内容的提问来源于stack exchange,提问作者Jason Kuhrt
相关产品推荐
相关产品推荐

