TypeScript编写嵌套数组通用排序函数的类型问题求解
泛型实现嵌套数组按指定属性排序
需求说明
通过泛型函数实现嵌套数组排序,排序依据为嵌套数组内元素的指定属性值。
示例数组结构
type Person = { id: number, name: string, childs: Child[] } type Child = { id: number, name: string, } const persons : Person[] = [ { id: 1, name: 'Person 1', childs:[ {id: 1, name: 'Child 1'}, {id: 2, name: 'Child 2'} ] }, { id: 2, name: 'Person 2', childs:[ {id: 1, name: 'Child 1'}, ] }, { id: 3, name: 'Person 3', childs:[ {id: 1, name: 'Child 1'}, {id: 2, name: 'Child 2'}, {id: 3, name: 'Child 3'} ] }, ];
注:原示例代码第三个Person对象的childs数组中
{id: 2, name: 'Child 2'}后缺少逗号,已修正。
期望调用形式
sortNestedArrays(persons, 'childs', 'name');
调用含义:将对象中键为childs的嵌套数组,按照元素的name属性进行排序。
当前实现问题
当前通过KeysMatching工具类型实现了keyOfSubArray参数的约束,仅允许传入值为数组类型的键,以保障嵌套数组的安全访问,代码如下:
type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never; type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T]; /** * @param array 主数组 * @param keyOfSubArray 主数组元素中指向嵌套数组的键名 * @param propertyName 嵌套数组排序所依据的属性键名 */ function sortNestedArrays<A, K extends KeysMatching<A, unknown[]>>(array: A[], keyOfSubArray: K, propertyName: ???){ array.forEach((member) => { const nestedArray = member[keyOfSubArray]; nestedArray.forEach((nestedMember) => { }); }); }
但TypeScript无法正确推导后续代码的类型,编译器抛出如下错误:
Property 'forEach' does not exist on type 'A[K]'.
const nestedArray = member[keyOfSubArray]; nestedArray.forEach((nestedMember) => { });
待解决问题
- 为什么编译器无法识别
member[keyOfSubArray]一定为数组类型?该问题应如何解决? - 如何为
propertyName参数定义正确的泛型约束,要求该参数必须是嵌套数组成员的键(对应示例中为keyof Child)?
问题解答
类型推导失败原因
编译器无法识别类型的核心原因是泛型参数的关联度不足:KeysMatching<A, unknown[]>仅单独约束了K的取值范围,但TypeScript不会在多泛型参数场景下自动做跨参数的类型收窄,不会默认推导A[K]一定满足数组类型(存在联合类型等边缘场景的类型兼容问题)。
实现思路
调整泛型约束逻辑,将「K对应的值必须是数组」的约束直接写在keyOfSubArray的参数类型位置,让TS能够建立参数和值的直接类型关联;同时新增泛型参数指代嵌套数组的元素类型,将propertyName的类型直接约束为该嵌套元素类型的键,TS会根据传入的keyOfSubArray自动推导嵌套元素的类型,实现完全的类型安全。
完整可运行实现
type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never; /** * 嵌套数组排序泛型函数 * @param array 主数组 * @param keyOfSubArray 主数组元素中指向嵌套数组的键名 * @param propertyName 嵌套数组排序所依据的属性键名 */ function sortNestedArrays< T, K extends keyof T, NestedItem extends ArrayElement<T[K]> >( array: T[], keyOfSubArray: T[K] extends readonly unknown[] ? K : never, propertyName: keyof NestedItem ) { array.forEach((member) => { // 此处断言为完全类型安全,泛型约束已保证值类型匹配 const nestedArray = member[keyOfSubArray] as unknown as NestedItem[]; // 通用排序逻辑,支持字符串、数字类型属性排序 nestedArray.sort((a, b) => { const valA = a[propertyName]; const valB = b[propertyName]; if (typeof valA === 'string' && typeof valB === 'string') { return valA.localeCompare(valB); } if (typeof valA === 'number' && typeof valB === 'number') { return valA - valB; } return 0; }); }); } // 合法调用,TS正常推导类型 sortNestedArrays(persons, 'childs', 'name'); // 非法调用会直接报类型错误 // sortNestedArrays(persons, 'id', 'name'); // 报错:id对应的值不是数组 // sortNestedArrays(persons, 'childs', 'age'); // 报错:Child类型不存在age属性
内容的提问来源于stack exchange,提问作者michaelT
相关产品推荐
相关产品推荐

