如何在TypeScript中定义符合Json约束的泛型函数数组类型
你遇到的问题本质是TypeScript没有直接提供「存在泛型」的语法支持,你需要的类型等价于「存在任意符合Json约束的类型A,使得当前值是Foo类型」,而TS原生泛型默认是「对所有符合约束的A都成立」,这就是为什么直接写Foo<Json>[]会报错的原因:函数参数类型是逆变的,Foo<number>、Foo<string>这类具体类型无法赋值给更宽泛的Foo<Json>类型。
以下是两种可行的实现方案:
方案1:使用辅助构造函数(推荐)
通过泛型辅助函数自动推导数组内每个元素的类型,同时做合法性校验,不需要手动定义联合类型:
type Json = string | number | boolean | null | Json[] | { [property: string]: Json }; type Foo<A extends Json> = (n: A) => boolean; // 辅助构造函数,入参是你要传入的Foo数组,返回值自动加约束 function createFooArray<T extends Foo<any>[]>(arr: [...T]) { // 约束每个元素的泛型参数必须符合Json要求 type ValidFoo<T> = T extends Foo<infer A> ? A extends Json ? T : never : never; return arr as ValidFoo<T[number]>[]; } // 示例:合法数组,无报错 const a: Foo<number> = (n) => n + 1 > 0; const b: Foo<string> = (n) => n.endsWith("a"); const foos = createFooArray([a, b]); // 示例:非法数组会触发类型报错 const invalidFoos = createFooArray([a, b, (n: HTMLElement) => true]); // 报错原因:HTMLElement不满足Json类型约束
这个方案零运行时开销,编译后辅助函数会被优化为普通数组,同时完整保留类型校验能力。
方案2:直接定义通用Foo类型
如果你不想使用辅助函数,也可以直接定义覆盖所有合法场景的SomeFoo类型:
type Json = string | number | boolean | null | Json[] | { [property: string]: Json }; type Foo<A extends Json> = (n: A) => boolean; type SomeFoo = Foo<Json> extends Foo<infer A> ? A extends Json ? Foo<A> : never : never; // 合法 const foos: SomeFoo[] = [a, b]; // 非法会报错 const invalidFoos: SomeFoo[] = [a, b, (n: HTMLElement) => true];
成对函数约束场景实现
针对你提到的「第一个函数返回值与第二个函数入参类型匹配」的业务场景,同样可以用辅助构造函数实现:
type Json = string | number | boolean | null | Json[] | { [property: string]: Json }; // 定义成对函数的类型 type SerializePair<A extends Json, B extends Json> = { serialize: (input: A) => B, deserialize: (input: B) => A } // 辅助构造函数 function createPairArray<T extends SerializePair<any, any>[]>(arr: [...T]) { type ValidPair<T> = T extends SerializePair<infer A, infer B> ? A extends Json ? B extends Json ? T : never : never : never; return arr as ValidPair<T[number]>[]; } // 合法用法 const pairs = createPairArray([ { serialize: (n: number) => n.toString(), deserialize: (s: string) => parseInt(s) }, { serialize: (arr: number[]) => arr.join(","), deserialize: (str: string) => str.split(",").map(Number) } ]);
内容的提问来源于stack exchange,提问作者Jason Desrosiers
相关产品推荐
相关产品推荐

