如何根据第一个参数的类型定义回调函数的签名?
如何根据第一个参数的类型定义回调函数的签名?
看起来你遇到的问题是泛型约束在处理对象类型时,没办法正确推断出回调里值的类型对吧?我来帮你分析下问题所在,然后给出修复方案。
问题根源
你原来的each函数泛型定义里,V是一个独立的泛型参数,当传入Record<string, Date>这类对象时,TypeScript没办法把V和对象的属性值类型(也就是Date)正确关联起来——因为T extends Record<K, V>这个约束比较松散,V可以被推断为任意类型,最终就变成了unknown。
修复方案
我们可以直接从传入的data类型T中推导回调参数的类型,不需要依赖独立的V、K泛型参数。这里有两种简洁的实现方式:
方式一:使用条件类型的泛型函数
直接通过条件类型分别处理数组和对象的情况,从T中提取值和键的类型:
declare const each: <T extends Array<any> | Record<PropertyKey, any>>( data: T, callback: T extends Array<infer V> ? (d: V, k: number) => void : (d: T[keyof T], k: keyof T) => void ) => void;
方式二:使用函数重载
这种方式更直观,分别为数组和对象场景定义重载签名:
// 数组类型的重载 declare function each<V>(data: V[], callback: (d: V, k: number) => void): void; // 对象类型的重载 declare function each<T extends Record<PropertyKey, any>>(data: T, callback: (d: T[keyof T], k: keyof T) => void): void;
验证效果
修复后再运行你的示例代码,两种场景都能正确推断类型了:
function bar(arr: Array<Set<number>>, obj: Record<string, Date>) { each(arr, (v, k) => { // ✅ v 是 Set<number>, k 是 number v.add(1); // 可以正常调用 Set 的方法 }); each(obj, (v, k) => { // ✅ v 是 Date, k 是 string console.log(v.getFullYear()); // 可以正常调用 Date 的方法 }); }
核心思路
不管用哪种方式,核心都是让回调函数的参数类型直接依赖于第一个参数data的类型T,而不是用独立的泛型参数间接关联,这样TypeScript就能准确完成类型推断啦。
备注:内容来源于stack exchange,提问作者Guile
相关产品推荐
相关产品推荐

