TypeScript:groupingBy函数动态子类型适配问题
解决groupingBy函数根据Seed键类型动态返回类型的问题
问题概述
你编写的groupingBy函数在seed传入包含特定字符串子类型键(如'big')的对象时,能正确返回「固定键必选数组 + 其他字符串键可选数组」的联合类型;但当seed的键类型为泛型string时,返回类型错误允许无检查的键访问,导致像grouped.small.forEach(...)这类不存在的键调用出现运行时错误。
解决方案
通过条件类型精准判断SeedKey的类型,动态生成对应的返回类型,修改后的函数实现如下:
// 定义条件类型,根据SeedKey的类型分支返回结果类型 type GroupingResult<SeedKey extends string, Value> = SeedKey extends string ? { [key: string]: Value[] | undefined } : { [key in SeedKey]: Value[] } & { [key: string]: Value[] | undefined }; function groupingBy<Item, Value, SeedKey extends string>( list: Item[], classifier: (item: Item) => string, mapper: (item: Item) => Value, seed: { [key in SeedKey]: Value[] } ): GroupingResult<SeedKey, Value> { return list.reduce((accumulator, item) => { const key = classifier(item); // 断言key为accumulator的键类型,避免类型报错 (accumulator[key as keyof typeof accumulator] = accumulator[key as keyof typeof accumulator] || []).push(mapper(item)); return accumulator; }, seed as GroupingResult<SeedKey, Value>); // 断言seed为目标结果类型 }
测试验证
场景1:seed包含特定子类型键
const identity = <T>(x: T) => x; const numbers = [1, 2, 3, 4, 10, 100, 44, 22]; const grouped = groupingBy(numbers, (num) => (num >= 10 ? 'big' : 'small'), identity, { big: [] }); // 返回类型:{ big: number[] } & { [key: string]: number[] | undefined } grouped.big.forEach((num) => console.log(num > 10)); // 正常,无需可选链 grouped.small?.forEach((num) => console.log(num <= 10)); // 需要可选链,符合预期
场景2:seed的键类型为泛型string
const numbers2 = [10, 100, 44, 22]; const seed: { [key: string]: number[] } = {}; const grouped2 = groupingBy(numbers2, (num) => (num >= 10 ? 'big' : 'small'), identity, seed); // 返回类型:{ [key: string]: number[] | undefined } grouped2.big?.forEach((num) => console.log(num > 10)); // 需要可选链,避免无检查访问 // grouped2.small.forEach(...) 会触发类型错误,提示small可能为undefined,符合预期
原理说明
- 条件类型
GroupingResult会在编译时判断SeedKey是否完全等于string:- 若是,返回仅包含可选字符串键的类型,强制所有键访问都需做存在性检查;
- 若否(即
SeedKey是string的子类型,如'big'),返回原有联合类型,保证固定键的必选访问权限。
- 相比函数重载,条件类型能直接在泛型层面做精准的分支判断,避免重载匹配时的类型覆盖问题,确保返回类型完全符合seed的键类型特征。
内容的提问来源于stack exchange,提问作者C Taylor
相关产品推荐
相关产品推荐

