TypeScript 实现排序函数时如何从提取器类型推断关联值类型
通用排序函数的TypeScript类型推断问题梳理
需求背景
我们需要实现一个通用排序函数,核心能力是支持用户通过自定义函数,从待排序的对象中提取用于排序的依据字段,理想状态下应该能顺着提取函数的返回值,自动给比较器的参数匹配对应的类型,全程不需要手动补充类型标注。
最初实现的问题
我们最初为了实现泛型约束,定义了提取函数类型、配置对象接口:
// 提取函数的基础类型约束 type TKey<TData> = (data: TData) => any interface SortingConfig< TData, TExtractor extends TKey<TData>, // 尝试通过条件类型从提取函数的签名里推导返回值类型 TValue = TExtractor extends (data: TData) => infer R ? R : never > { extractor: TExtractor, comparator?: (a: TValue, b: TValue) => number } // 接收数据和配置对象的函数定义 function test1<TData, TExtractor extends TKey<TData>>( value: TData, conf: SortingConfig<TData, TExtractor> ) {}
实际调用时出现了明显的推断割裂,具体表现为:
- 传入待排序对象后,
extractor的入参data能正确匹配为传入对象的类型(比如传入{a: number, b: string}类型的对象时,data会被推断为这个结构) - 但关联的
comparator参数完全丢失类型,自动退化成(a: any, b: any) => any,完全没有拿到extractor返回值的类型
异常表现示例:
test1({ a: 1, b: "string" }, { // 这里extractor返回的是string类型(取b字段) extractor: data => data.b, // 期望comparator的参数是string类型,实际是any comparator: (a, b) => a })
对照测试发现的正常场景
我们单独做了参数拆分的测试,把extractor和comparator从配置对象里拿出来作为独立函数参数传入,类型推断就完全正常了:
function test2< TData, TExtractor extends TKey<TData>, TValue = TExtractor extends (data: TData) => infer R ? R : never >( data: TData, extractor: TExtractor, comparator: (a: TValue, b: TValue) => number ) {} // 调用时类型完全匹配:extractor取a字段返回number,comparator的a、b参数自动推断为number类型 test2({a: 1, b: "string"}, data => data.a, (a, b) => a)
但这种拆分参数的写法不符合我们的配置聚合设计要求。
调整方案的缺陷
我们调整了泛型定义的逻辑,去掉了单独的TExtractor泛型参数,直接从extractor的函数签名推导TValue:
function test1<TData, TValue>( value: TData, conf: { extractor: (data: TData) => TValue, comparator: (a: TValue, b: TValue) => number } ) {}
这种写法下comparator的类型推断恢复正常,但彻底丢失了extractor入参的自动类型推断能力:如果调用时不手动给extractor的data参数标注类型,TS会直接把data推断成any,没有类型保护。
内容的提问来源于stack exchange,提问作者295
相关产品推荐
相关产品推荐

