如何放宽泛型类型声明以避免推断出unknown类型
我太懂你遇到的这个痛点了——写了个带泛型的groupBy函数,正常场景下用得好好的,可一旦碰到隐式any类型的数组,TypeScript就把元素推断成unknown,导致属性访问直接报错。关键你还不想在每个调用处都加类型断言,改得麻烦死了对吧?咱们直接调整groupBy的泛型声明,就能完美解决这个问题,不用动任何调用代码。
先复盘下问题出在哪:
你原来的groupBy泛型参数ListValueType没有设置默认类型,在TypeScript的严格模式下,当传入的list是隐式any类型时,TypeScript会优先选择更安全的unknown作为推断结果,于是keyGetter里的elem就变成了unknown,自然没法访问myProperty这种属性。而如果你把所有类型都删掉,函数参数默认是any,反而不会有这个问题——本质就是我们需要让泛型在无法推断具体类型时, fallback 到any而非unknown。
直接上修改方案,只需要给ListValueType加个默认类型any就行,同时把KeyValueType的约束改成更规范的小写原始类型(原来的大写String/Number是构造函数类型,日常用原始类型更合适):
function groupBy<ListValueType = any, KeyValueType extends string | number>( list: ListValueType[], keyGetter: (elem: ListValueType) => KeyValueType ): Map<KeyValueType, ListValueType[]> { const map = new Map<KeyValueType, ListValueType[]>(); list.forEach((item) => { const key = keyGetter(item); const collection = map.get(key); if (!collection) { map.set(key, [item]); } else { collection.push(item); } }); return map; }
咱们来验证下效果:
- 原来的正常类型调用完全不受影响,类型推断依然精准:
const arr = [1,2,3,4,5,6]; const map = groupBy(arr, x => x < 4 ? 'small' : 'large'); // map的类型还是Map<'small'|'large', number[]>,类型安全拉满
- 你之前报错的隐式
any场景现在完全正常:
function someFunction(list) { // list是隐式any类型 const map = groupBy(list, elem => elem.myProperty); // 再也不会出现TS18046错误了!elem会被推断为any }
而且这种修改完全保留了原来的类型安全——当你传入明确类型的数组时,TypeScript依然会严格检查类型。比如你传一个带明确接口的对象数组,elem会自动推断为接口类型,属性拼写错了照样会报错:
interface User { role: string; } const users: User[] = [{role: 'admin'}, {role: 'user'}]; const userMap = groupBy(users, elem => elem.role); // userMap的类型是Map<string, User[]>,完全符合预期
核心逻辑就是给泛型参数加默认值,让TypeScript在无法推断具体类型时,用any替代unknown,完美兼容你不想修改调用代码的需求。
内容来源于stack exchange

