TypeScript如何提取对象值为字面量联合类型实现类型安全groupBy函数
报错原因与基础修复
你遇到的Type 'T[U]' is not assignable to type 'string | number | symbol'报错,是因为T extends Record<string, unknown>只约束了T的键是字符串,没有约束属性值的类型:unknown包含了对象、函数等不能作为对象键的类型,自然不符合索引签名的要求。
只需要给泛型加一层属性值的约束即可修复基础报错:
// 基础修复版,解决类型报错 export const groupBy = < T extends Record<string, unknown>, U extends keyof T, // 加约束:T[U]必须是可以作为对象键的类型 V extends PropertyKey = T[U] & PropertyKey >( objArr: T[], property: U, ): { [key in V]: T[] } => objArr .reduce((memo, x) => { const value = x[property] as V; if (!memo[value]) { memo[value] = []; } memo[value].push(x); return memo; }, {} as { [key in V]: T[] });
实现精确字面量键类型
要自动提取输入数组属性的字面量类型作为返回键,需要用到TS 5.0及以上版本支持的const泛型,它可以在函数层面实现类似as const的类型收窄效果,不需要用户手动给输入加断言:
// 精确类型版,自动收窄字面量类型作为返回键 export const groupBy = < // const修饰泛型,自动收窄输入的字面量类型 const T extends readonly Record<string, unknown>[], U extends keyof T[number], // 约束属性值为合法键类型 V extends PropertyKey = T[number][U] & PropertyKey >( objArr: T, property: U, ): { [key in V]: Array<T[number]> } => objArr .reduce((memo, x) => { const value = x[property] as V; if (!memo[value]) { memo[value] = []; } memo[value].push(x); return memo; }, {} as { [key in V]: Array<T[number]> });
调用效果验证
用你的示例代码直接调用,不需要加as const就能得到精确返回类型:
const data = [ { name: 'corn cob', value: 17, group: 'item' }, { name: 'Dirty toilet', value: 6, group: 'item' }, { name: 'snake', value: 2, group: 'animal' }, { name: 'tesla', value: 17, group: 'car' }, { name: 'gurgel', value: 23, group: 'car' }, ]; const result = groupBy(data, 'group'); // result类型自动推导为: // { // item: { name: string; value: number; group: 'item' }[]; // animal: { name: string; value: number; group: 'animal' }[]; // car: { name: string; value: number; group: 'car' }[]; // }
如果你的TS版本低于5.0,无法使用const泛型,只需要保留上面的泛型约束写法,用户调用时手动给输入数组加as const断言,也能得到完全一致的精确类型效果。
内容的提问来源于stack exchange,提问作者Omar Omeiri
相关产品推荐
相关产品推荐

