数组里面的元素要一模一样吗?不一样可以吗?

7 阅读3分钟

不要求“一模一样”,但要看 TS 怎么推导 T:

1)数组元素都同类型:最简单

head([1, 2, 3])    // T = number
head(['a', 'b'])   // T = string

2)数组元素不同类型:也可以,TS 会把 T 推成“联合类型”

head([1, 'a', true]) // T = number | string | boolean
// 返回类型就是 (number | string | boolean) | undefined

3)如果你想“限制必须同类型”

那就不要让它推成联合:通常通过 你自己声明数组类型 或约束来实现,例如:

const nums: number[] = [1, 2, 3]  // 强制元素都是 number

如果你写:

const nums: number[] = [1, 'a'] // ❌ 报错

工具方法本身写成泛型时,TS 允许混合数组,是因为它会把 T 推成联合类型(例如 T = number | string)。如果你想“工具方法直接拒绝混合数组”,有两种常用做法:

做法 A(最实用):让调用方给出“单一数组类型”

工具方法正常写:

function head<T>(list: T[]): T | undefined {
  return list[0]
}

调用时通过变量类型或显式泛型来强制同类型:

const nums: number[] = [1, 2, 3]
head(nums) // ✅ T = number

head<number>([1, 2, 3]) // ✅

head<number>([1, 'a'])  // ❌ 直接报错

做法 B(更严格):工具方法层面禁止 T 是联合类型(禁止混合)

type IsUnion<T, U = T> =
  T extends any ? ([U] extends [T] ? false : true) : never

type NoUnion<T> = IsUnion<T> extends true ? never : T

function headStrict<T>(list: NoUnion<T>[]): NoUnion<T> | undefined {
  return list[0]
}

headStrict([1, 2, 3])     // ✅
headStrict(['a', 'b'])    // ✅
headStrict([1, 'a'])      // ❌(T 会被推成 number | string,被禁止)

一般业务里推荐 做法 A(更直观、团队更好维护);做库/想强约束再用 做法 B。

总结

  • 可以不一样:TS 会推导出联合类型 T = A | B | ...
  • 想必须一样:就把数组声明为 number[] / string[] 这种单一元素类型数组

额外:有联合类型,就有阻止联合。

联合类型一般是 TS “没法确定只有一种类型”时自动推出来的(比如 [1, 'a'] → T = number | string)。要“阻止联合”,常用有两类办法:

1)在调用处阻止(最常用):显式指定“必须是单一类型”

  • 给数组标注类型(推荐)
const nums: number[] = [1, 2, 3]
// const nums: number[] = [1, 'a'] // ❌ 直接报错

head(nums) // T = number
  • 调用时显式指定泛型
head<number>([1, 2, 3]) // ✅
head<number>([1, 'a'])  // ❌

2)在工具函数层面强制禁止(更严格):让 T 不能是联合

type IsUnion<T, U = T> =
  T extends any ? ([U] extends [T] ? false : true) : never

type NoUnion<T> = IsUnion<T> extends true ? never : T

function headStrict<T>(list: NoUnion<T>[]): NoUnion<T> | undefined {
  return list[0]
}

headStrict([1, 2, 3])   // ✅
headStrict([1, 'a'])    // ❌(推导出 T=number|string,被禁止)、

它的目标是:如果 TS 推导出 T 是联合类型(比如 number | string),就让它变成 never,从而在调用处报错,达到“禁止混合数组”的效果。

通常业务里用 第 1 种就够了;做通用库、想强约束再用 第 2 种。

思路: “阻止联合”的本质逻辑就是一句话:让 TS 在推导时就能确定:只能有一种类型(单一 T),不能出现第二种可能。

怎么做到“只能一种类型”(两条路)

  • 路 1:在输入处就限定范围(最常用)

    • 给变量/参数明确类型:number[]、string[]
    • 或者调用时手动指定泛型:head<number>(...)
    • 逻辑:你提前把“类型集合”定死了,TS 就不需要用 A | B 来兜底。
  • 路 2:在工具函数签名里写规则,直接拒绝联合(更严格)

    • 用类型体操判断 T 是否为联合
    • 如果是联合就变 never 让调用报错
    • 逻辑:允许 TS 推导出联合,但你声明“联合不合法”,让它在类型检查阶段失败。