You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

泛型与数组:为何得到`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>
}

关键调整说明

  1. 条件类型ToCapitalizedResult<T>:明确判断输入T是否为数组类型,如果是则返回元素类型的CapitalizedProps数组,否则返回单个元素的CapitalizedProps类型,直接建立输入输出的类型对应关系。
  2. 泛型参数范围调整:将T的范围设为Item | Item[],引导TypeScript正确推断数组的元素类型,而非将整个数组当作T。
  3. 移除冗余重载:通过条件类型已经可以精准描述返回类型,无需额外重载,避免类型推断冲突。

验证示例

// 单个对象输入,返回单个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 01:14:58