实现元组转联合

84 阅读2分钟

题目描述 🎯

实现泛型TupleToUnion<T>,它返回元组所有值的联合类型。

type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // '1' | '2' | '3'

题目分析 📝

  1. 输入:一个元组类型T

  2. 输出:元组中所有元素的联合类型

  3. 需要处理的情况:

    • 空元组
    • 包含不同类型元素的元组
    • 单个元素的元组

解题思路 💡

有两种主要的实现思路:

  1. 使用索引访问类型:

    • 利用T[number]可以获取数组(元组)所有元素的联合类型
  2. 使用infer递归:

    • 使用infer依次提取元组中的元素
    • 通过递归构建联合类型

代码实现 ⌨️

方法一:索引访问类型

type TupleToUnion<T extends any[]> = T[number]

方法二:infer 递归

type TupleToUnion<T extends readonly any[]> = T extends [infer F, ...infer R] ? F | TupleToUnion<R> : never

题解详情 🔍

方法一:索引访问类型解析

type TupleToUnion<T extends readonly any[]> = T[number]
  1. T extends readonly any[]

    • 限制T必须是一个只读的数组类型

2.T[number]

  • 使用数字索引访问类型
    • TypeScript会自动将数组/元组的所有可能索引值对应的类型联合起来

为什么使用 readonly any[] ?

使用readonly any[]可以接受更多类型的参数:

  • 可以接受普通数组/元组
  • 可以接受只读数组/元组
  • 提供更好的类型兼容性
// 使用 any[]
type TupleToUnion1<T extends any[]> = T[number];

// 使用 readonly any[]
type TupleToUnion2<T extends readonly any[]> = T[number];

// 定义一个只读元组
const tuple = ['1', '2', '3'] as const;
// tuple 的类型是 readonly ['1', '2', '3']

// 使用 any[]
type Test1 = TupleToUnion1<typeof tuple>; // ❌ 类型错误
// The type 'readonly ["1", "2", "3"]' is 'readonly' and cannot be assigned to the mutable type 'any[]'.

// 使用 readonly any[]
type Test2 = TupleToUnion2<typeof tuple>; // ✅ 正常工作
// type Test2 = "1" | "2" | "3"

方法二:infer递归解析

type TupleToUnion<T extends readonly any[]> = T extends [infer F, ...infer R] ? F | TupleToUnion<R> : never
  1. T extends [infer F, ...infer R]

    • 使用infer提取元组的第一个元素(F)和剩余元素(R)
  2. F | TupleToUnion<R>

    • 将第一个元素与递归处理剩余元素的结果联合
  3. :never

    • 当元组为空时返回never

示例分析 🌟

让我们通过一个具体示例来看执行过程:

type Arr = ['1', '2', '3']
type Result = TupleToUnion<Arr>

// 方法一:索引访问类型
type Result1 = Arr[number]
// 直接得到 '1' | '2' | '3'

// 方法二:infer 递归
// 步骤 1:
T extends ['1', '2', '3']
F = '1'
R = ['2', '3']
Result = '1' | TupleToUnion<['2', '3']>

// 步骤 2:
T extends ['2', '3']
F = '2'
R = ['3']
Result = '1' | '2' | TupleToUnion<['3']>

// 步骤 3:
T extends ['3']
F = '3'
R = []
Result = '1' | '2' | '3' | TupleToUnion<[]>

// 步骤 4:
T extends [] = false
Result = '1' | '2' | '3' | never
// 最终得到 '1' | '2' | '3'

测试示例 👀

type TupleToUnion1<T extends readonly any[]> = T[number];
type TupleToUnion2<T extends readonly any[]> = 
  T extends [infer F, ...infer R] 
    ? F | TupleToUnion2<R>
    : never;
// 测试空元组
type EmptyTuple = []
type EmptyResult1 = TupleToUnion1<EmptyTuple> // never
type EmptyResult2 = TupleToUnion2<EmptyTuple> // never

// 测试混合类型元组
type MixedTuple = [string, number, boolean]
type MixedResult1 = TupleToUnion1<MixedTuple> // string | number | boolean
type MixedResult2 = TupleToUnion2<MixedTuple> // string | number | boolean

// 测试单元素元组
type SingleTuple = [string]
type SingleResult1 = TupleToUnion1<SingleTuple> // string
type SingleResult2 = TupleToUnion2<SingleTuple> // string