如何为TypeScript中的对象聚合函数添加类型定义
为对象聚合函数添加正确的TypeScript类型定义
函数(JavaScript实现)
该函数接收初始对象和任意数量的新对象,将所有对象中同键名的数组合并:
export function aggregateObjects(initialObject, ...newObjects) { const aggregate = (objectA, objectB) => { return Object.keys(objectB).reduce( (acc, key) => ({ ...acc, [key]: [...(acc[key] ?? []), ...(objectB[key] ?? [])], }), objectA ); }; return newObjects.reduce( (aggregatedObjects, newObject) => aggregate(aggregatedObjects, newObject), initialObject ); }
TypeScript使用示例
函数支持传入任意数量的新对象,正确调用方式如下:
const initialObject = { a: [objectOfType1], }; const newObject1 = { a: [objectOfType2], b: [objectOfType1], }; const newObject2 = { c: [objectOfType1, objectOfType2], }; const result = aggregateObjects(initialObject, newObject1, newObject2);
期望的TypeScript类型结果
基于上述示例,返回值类型应自动推导为:
type Result = { a: (ObjectType1 | ObjectType2)[]; b: ObjectType1[]; c: (ObjectType1 | ObjectType2)[]; };
尝试过的方案(存在问题)
以下方案无法正确推导返回值类型,且内部需要手动忽略类型检查:
export function aggregateObjects<T extends object, U extends any[]>( initialObjects: T, ...newObjects: U ) { const aggregate = <A extends object, B extends object>( objectA: A, objectB: B ): CombineObjs<A, B> => { return (Object.keys(objectB) as Array<keyof B>).reduce( (acc, key) => ({ ...acc, [key]: [ // @ts-ignore ...(acc[key as keyof CombineObjs<A, B>] ?? []), // @ts-ignore ...(objectB[key] ?? []), ], }), objectA as unknown as CombineObjs<A, B> ); }; return newObjects.reduce( (aggregatedObjects, newObject) => aggregate(aggregatedObjects, newObject), initialObjects ); }
正确解决方案
我们需要定义递归的类型辅助工具,用来合并多个对象的键值类型(将同键的数组合并为联合类型数组),同时为函数添加正确的泛型约束:
步骤1:定义单个键的数组类型合并规则
type MergeArrayValues<T, U> = T extends Array<infer TItem> ? U extends Array<infer UItem> ? (TItem | UItem)[] : T : U extends Array<infer UItem> ? U : never;
步骤2:定义两个对象的合并类型
type MergeObjects<T extends object, U extends object> = { [K in keyof T | keyof U]: K extends keyof T ? K extends keyof U ? MergeArrayValues<T[K], U[K]> : T[K] : K extends keyof U ? U[K] : never; };
步骤3:定义任意数量对象的递归合并类型
type MergeAllObjects<T extends object, U extends object[]> = U extends [infer First extends object, ...infer Rest extends object[]] ? MergeAllObjects<MergeObjects<T, First>, Rest> : T;
步骤4:为函数添加泛型类型约束
export function aggregateObjects< T extends Record<string, unknown[]>, U extends Record<string, unknown[]>[] >(initialObject: T, ...newObjects: U): MergeAllObjects<T, U> { const aggregate = <A extends Record<string, unknown[]>, B extends Record<string, unknown[]>>( objectA: A, objectB: B ): MergeObjects<A, B> => { return (Object.keys(objectB) as Array<keyof B>).reduce((acc, key) => { const existing = acc[key] ?? []; const incoming = objectB[key] ?? []; return { ...acc, [key]: [...existing, ...incoming] as MergeArrayValues<A[keyof A], B[keyof B]> }; }, objectA as unknown as MergeObjects<A, B>); }; return newObjects.reduce( (aggregated, obj) => aggregate(aggregated, obj), initialObject as unknown as MergeAllObjects<T, U> ); }
验证示例
使用示例代码时,TypeScript会自动推导出正确的返回值类型:
type ObjectType1 = { id: number }; type ObjectType2 = { name: string }; const initialObject = { a: [{ id: 1 } as ObjectType1], }; const newObject1 = { a: [{ name: "test" } as ObjectType2], b: [{ id: 2 } as ObjectType1], }; const newObject2 = { c: [{ id: 3 } as ObjectType1, { name: "foo" } as ObjectType2], }; const result = aggregateObjects(initialObject, newObject1, newObject2); // result类型自动推导为: // { // a: (ObjectType1 | ObjectType2)[]; // b: ObjectType1[]; // c: (ObjectType1 | ObjectType2)[]; // }
内容的提问来源于stack exchange,提问作者Thibault Boursier
相关产品推荐
相关产品推荐

