泛型与数组:为何得到`Generic<T[]>`而非`Generic<T>[]`?
解决TypeScript中"单值/数组输入对应单值/数组输出"的类型推断问题
问题描述
我实现了一个函数,它接受单个T类型对象或T[]类型数组,执行逻辑后返回与输入类型匹配的结果(传单个项返回单个结果,传数组返回结果数组)。JS运行逻辑完全符合预期,但TypeScript类型系统始终将数组嵌套在泛型内部,无法得到预期的类型解析结果。
原函数代码:
type OneOrMany<T> = T | T[] type Item = Record<string, any> type CapitalizedProps<T extends Item> = { [K in keyof T as Capitalize<K & string>]: T[K] } function toCapitalizedProps<T extends Item>(item: T): CapitalizedProps<T> function toCapitalizedProps<T extends Item>(items: T[]): CapitalizedProps<T>[] function toCapitalizedProps<T extends Item>( itemOrItems: OneOrMany<T>, ): OneOrMany<CapitalizedProps<T>> { if (Array.isArray(itemOrItems)) { return itemOrItems.map((item) => toCapitalizedProps(item), ) as CapitalizedProps<T>[] } const result = { ...itemOrItems } for (const key in result) { result[(key[0].toUpperCase() + key.slice(1)) as keyof T] = result[key] delete result[key] } return result as unknown as CapitalizedProps<T> }
问题原因
当前的泛型定义和重载组合存在类型推断歧义:当传入数组时,TypeScript可能会将整个数组类型推断为T,而非将数组元素类型推断为T。此时OneOrMany<CapitalizedProps<T>>会被解析为CapitalizedProps<T[]> | CapitalizedProps<T>,而非预期的CapitalizedProps<T>[] | CapitalizedProps<T>,导致类型嵌套错误。
解决方案
我们可以通过条件类型直接关联输入和输出的类型映射,同时调整泛型参数的定义,让TypeScript正确推断元素类型而非容器类型:
type Item = Record<string, any> type CapitalizedProps<T extends Item> = { [K in keyof T as Capitalize<K & string>]: T[K] } // 用条件类型定义输入到输出的映射 type ToCapitalizedResult<T> = T extends Item[] ? CapitalizedProps<T[number]>[] : CapitalizedProps<T> function toCapitalizedProps<T extends Item | Item[]>( itemOrItems: T ): ToCapitalizedResult<T> { if (Array.isArray(itemOrItems)) { return itemOrItems.map(item => { const result = { ...item } for (const key in result) { const capitalizedKey = (key[0].toUpperCase() + key.slice(1)) as keyof CapitalizedProps<typeof item> result[capitalizedKey] = result[key] delete result[key] } return result as CapitalizedProps<typeof item> }) as ToCapitalizedResult<T> } const result = { ...itemOrItems } for (const key in result) { const capitalizedKey = (key[0].toUpperCase() + key.slice(1)) as keyof CapitalizedProps<T> result[capitalizedKey] = result[key] delete result[key] } return result as ToCapitalizedResult<T> }
关键调整说明
- 条件类型
ToCapitalizedResult<T>:明确判断输入T是否为数组类型,如果是则返回元素类型的CapitalizedProps数组,否则返回单个元素的CapitalizedProps类型,直接建立输入输出的类型对应关系。 - 泛型参数范围调整:将
T的范围设为Item | Item[],引导TypeScript正确推断数组的元素类型,而非将整个数组当作T。 - 移除冗余重载:通过条件类型已经可以精准描述返回类型,无需额外重载,避免类型推断冲突。
验证示例
// 单个对象输入,返回单个CapitalizedProps对象 const single = toCapitalizedProps({ name: "Alice", age: 30 }) // single类型:{ Name: string; Age: number } // 数组输入,返回CapitalizedProps数组 const list = toCapitalizedProps([{ name: "Bob" }, { age: 25 }]) // list类型:{ Name: string; }[] | { Age: number; }[]
内容的提问来源于stack exchange,提问作者Wilco
相关产品推荐
相关产品推荐

